diff --git a/.coveragerc b/.coveragerc index 73814938619..b7433ecf58a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -73,7 +73,6 @@ omit = homeassistant/components/arest/binary_sensor.py homeassistant/components/arest/sensor.py homeassistant/components/arest/switch.py - homeassistant/components/arlo/* homeassistant/components/arris_tg2492lg/* homeassistant/components/aruba/device_tracker.py homeassistant/components/arwn/sensor.py @@ -83,9 +82,7 @@ omit = homeassistant/components/aseko_pool_live/sensor.py homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_mbox/* - homeassistant/components/asuswrt/__init__.py homeassistant/components/asuswrt/diagnostics.py - homeassistant/components/asuswrt/router.py homeassistant/components/aten_pe/* homeassistant/components/atome/* homeassistant/components/aurora/__init__.py @@ -120,6 +117,7 @@ omit = homeassistant/components/bmw_connected_drive/__init__.py homeassistant/components/bmw_connected_drive/binary_sensor.py homeassistant/components/bmw_connected_drive/button.py + homeassistant/components/bmw_connected_drive/coordinator.py homeassistant/components/bmw_connected_drive/device_tracker.py homeassistant/components/bmw_connected_drive/lock.py homeassistant/components/bmw_connected_drive/notify.py @@ -215,7 +213,6 @@ omit = homeassistant/components/devolo_home_control/subscriber.py homeassistant/components/devolo_home_control/switch.py homeassistant/components/digital_ocean/* - homeassistant/components/digitalloggers/switch.py homeassistant/components/discogs/sensor.py homeassistant/components/discord/__init__.py homeassistant/components/discord/notify.py @@ -593,10 +590,6 @@ omit = homeassistant/components/keyboard_remote/* homeassistant/components/kira/* homeassistant/components/kiwi/lock.py - homeassistant/components/knx/__init__.py - homeassistant/components/knx/climate.py - homeassistant/components/knx/cover.py - homeassistant/components/knx/notify.py homeassistant/components/kodi/__init__.py homeassistant/components/kodi/browse_media.py homeassistant/components/kodi/const.py @@ -678,6 +671,9 @@ omit = homeassistant/components/map/* homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* + homeassistant/components/meater/__init__.py + homeassistant/components/meater/const.py + homeassistant/components/meater/sensor.py homeassistant/components/media_extractor/* homeassistant/components/mediaroom/media_player.py homeassistant/components/melcloud/__init__.py @@ -996,7 +992,8 @@ omit = homeassistant/components/rtorrent/sensor.py homeassistant/components/russound_rio/media_player.py homeassistant/components/russound_rnet/media_player.py - homeassistant/components/sabnzbd/* + homeassistant/components/sabnzbd/__init__.py + homeassistant/components/sabnzbd/sensor.py homeassistant/components/saj/sensor.py homeassistant/components/satel_integra/* homeassistant/components/schluter/* @@ -1030,6 +1027,10 @@ omit = homeassistant/components/sensibo/number.py homeassistant/components/sensibo/select.py homeassistant/components/sensibo/sensor.py + homeassistant/components/sensibo/update.py + homeassistant/components/senz/__init__.py + homeassistant/components/senz/api.py + homeassistant/components/senz/climate.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py @@ -1128,6 +1129,8 @@ omit = homeassistant/components/spotify/media_player.py homeassistant/components/spotify/system_health.py homeassistant/components/spotify/util.py + homeassistant/components/slimproto/__init__.py + homeassistant/components/slimproto/media_player.py homeassistant/components/squeezebox/__init__.py homeassistant/components/squeezebox/browse_media.py homeassistant/components/squeezebox/media_player.py @@ -1177,6 +1180,7 @@ omit = homeassistant/components/synology_dsm/camera.py homeassistant/components/synology_dsm/diagnostics.py homeassistant/components/synology_dsm/common.py + homeassistant/components/synology_dsm/entity.py homeassistant/components/synology_dsm/sensor.py homeassistant/components/synology_dsm/service.py homeassistant/components/synology_dsm/switch.py @@ -1201,7 +1205,7 @@ omit = homeassistant/components/tankerkoenig/const.py homeassistant/components/tankerkoenig/sensor.py homeassistant/components/tapsaff/binary_sensor.py - homeassistant/components/tautulli/const.py + homeassistant/components/tautulli/__init__.py homeassistant/components/tautulli/coordinator.py homeassistant/components/tautulli/sensor.py homeassistant/components/ted5000/sensor.py @@ -1265,6 +1269,7 @@ omit = homeassistant/components/tractive/__init__.py homeassistant/components/tractive/binary_sensor.py homeassistant/components/tractive/device_tracker.py + homeassistant/components/tractive/diagnostics.py homeassistant/components/tractive/entity.py homeassistant/components/tractive/sensor.py homeassistant/components/tractive/switch.py @@ -1276,6 +1281,9 @@ omit = homeassistant/components/tradfri/light.py homeassistant/components/tradfri/sensor.py homeassistant/components/tradfri/switch.py + homeassistant/components/trafikverket_ferry/__init__.py + homeassistant/components/trafikverket_ferry/coordinator.py + homeassistant/components/trafikverket_ferry/sensor.py homeassistant/components/trafikverket_train/__init__.py homeassistant/components/trafikverket_train/sensor.py homeassistant/components/trafikverket_weatherstation/__init__.py @@ -1365,6 +1373,7 @@ omit = homeassistant/components/vicare/button.py homeassistant/components/vicare/climate.py homeassistant/components/vicare/const.py + homeassistant/components/vicare/diagnostics.py homeassistant/components/vicare/__init__.py homeassistant/components/vicare/sensor.py homeassistant/components/vicare/water_heater.py @@ -1428,6 +1437,7 @@ omit = homeassistant/components/xiaomi_miio/button.py homeassistant/components/xiaomi_miio/device.py homeassistant/components/xiaomi_miio/device_tracker.py + homeassistant/components/xiaomi_miio/diagnostics.py homeassistant/components/xiaomi_miio/fan.py homeassistant/components/xiaomi_miio/gateway.py homeassistant/components/xiaomi_miio/humidifier.py @@ -1443,6 +1453,7 @@ omit = homeassistant/components/yale_smart_alarm/__init__.py homeassistant/components/yale_smart_alarm/alarm_control_panel.py homeassistant/components/yale_smart_alarm/binary_sensor.py + homeassistant/components/yale_smart_alarm/button.py homeassistant/components/yale_smart_alarm/const.py homeassistant/components/yale_smart_alarm/coordinator.py homeassistant/components/yale_smart_alarm/diagnostics.py @@ -1452,6 +1463,7 @@ omit = homeassistant/components/yamaha_musiccast/media_player.py homeassistant/components/yamaha_musiccast/number.py homeassistant/components/yamaha_musiccast/select.py + homeassistant/components/yamaha_musiccast/switch.py homeassistant/components/yandex_transport/* homeassistant/components/yeelightsunflower/light.py homeassistant/components/yi/camera.py @@ -1489,6 +1501,7 @@ omit = homeassistant/components/zwave_me/button.py homeassistant/components/zwave_me/cover.py homeassistant/components/zwave_me/climate.py + homeassistant/components/zwave_me/fan.py homeassistant/components/zwave_me/helpers.py homeassistant/components/zwave_me/light.py homeassistant/components/zwave_me/lock.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2f94441940e..ba2911dcf0c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,8 @@ "ms-python.vscode-pylance", "visualstudioexptteam.vscodeintellicode", "redhat.vscode-yaml", - "esbenp.prettier-vscode" + "esbenp.prettier-vscode", + "GitHub.vscode-pull-request-github" ], // Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json "settings": { diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 040f4a128ee..9667775e8e9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -78,12 +78,13 @@ body: - type: textarea attributes: label: Diagnostics information + placeholder: "drag-and-drop the diagnostics data file here (do not copy-and-paste the content)" description: >- Many integrations provide the ability to download diagnostic data on the device page (and on the integration dashboard). **It would really help if you could download the diagnostics data for the device you are having issues with, - and drag-and-drop that file into the textbox below.** + and drag-and-drop that file into the textbox below.** It generally allows pinpointing defects and thus resolving issues faster. - type: textarea diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index f365987b0d6..2a83ed47de0 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -24,12 +24,12 @@ jobs: publish: ${{ steps.version.outputs.publish }} steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 with: fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -67,10 +67,10 @@ jobs: if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -100,11 +100,11 @@ jobs: arch: ${{ fromJson(needs.init.outputs.architectures) }} steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} if: needs.init.outputs.channel == 'dev' - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -173,7 +173,7 @@ jobs: - tinker steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set build additional args run: | @@ -216,7 +216,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Initialize git uses: home-assistant/actions/helpers/git-init@master @@ -255,7 +255,7 @@ jobs: - "homeassistant" steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Login to DockerHub if: matrix.registry == 'homeassistant' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f447a83243e..c95c09bd175 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ on: env: CACHE_VERSION: 9 PIP_CACHE_VERSION: 3 - HA_SHORT_VERSION: 2022.4 + HA_SHORT_VERSION: 2022.5 DEFAULT_PYTHON: 3.9 PRE_COMMIT_CACHE: ~/.cache/pre-commit PIP_CACHE: /tmp/pip-cache @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Filter for core changes uses: dorny/paths-filter@v2.10.2 id: core @@ -152,10 +152,10 @@ jobs: pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Generate partial Python venv restore key @@ -172,7 +172,7 @@ jobs: env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: >- @@ -189,7 +189,7 @@ jobs: # ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}- - name: Restore pip wheel cache if: steps.cache-venv.outputs.cache-hit != 'true' - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PIP_CACHE }} key: >- @@ -212,7 +212,7 @@ jobs: hashFiles('.pre-commit-config.yaml') }}" - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} key: >- @@ -233,15 +233,15 @@ jobs: - prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ @@ -253,7 +253,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} @@ -283,15 +283,15 @@ jobs: - prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ @@ -303,7 +303,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} @@ -334,15 +334,15 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ @@ -354,7 +354,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} @@ -376,15 +376,15 @@ jobs: - prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ @@ -396,7 +396,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} @@ -435,11 +435,19 @@ jobs: . venv/bin/activate pre-commit run --hook-stage manual check-json --all-files - - name: Run prettier + - name: Run prettier (fully) + if: needs.changes.outputs.test_full_suite == 'true' run: | . venv/bin/activate pre-commit run --hook-stage manual prettier --all-files + - name: Run prettier (partially) + if: needs.changes.outputs.test_full_suite == 'false' + shell: bash + run: | + . venv/bin/activate + pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* + - name: Register check executables problem matcher run: | echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json" @@ -491,10 +499,10 @@ jobs: container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ matrix.python-version }}-${{ @@ -515,15 +523,15 @@ jobs: needs: prepare-base steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ @@ -550,7 +558,7 @@ jobs: container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Generate partial Python venv restore key id: generate-python-key run: >- @@ -565,7 +573,7 @@ jobs: env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: >- @@ -582,7 +590,7 @@ jobs: # ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}- - name: Restore pip wheel cache if: steps.cache-venv.outputs.cache-hit != 'true' - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: ${{ env.PIP_CACHE }} key: >- @@ -618,10 +626,10 @@ jobs: container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ matrix.python-version }}-${{ @@ -660,10 +668,10 @@ jobs: container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ matrix.python-version }}-${{ @@ -704,10 +712,10 @@ jobs: container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ matrix.python-version }}-${{ @@ -747,10 +755,10 @@ jobs: container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v3.0.0 + uses: actions/cache@v3.0.2 with: path: venv key: ${{ runner.os }}-${{ matrix.python-version }}-${{ @@ -835,14 +843,14 @@ jobs: - pytest steps: - name: Check out code from GitHub - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Download all coverage artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 - name: Upload coverage to Codecov (full coverage) if: needs.changes.outputs.test_full_suite == 'true' - uses: codecov/codecov-action@v2.1.0 + uses: codecov/codecov-action@v3.1.0 with: flags: full-suite - name: Upload coverage to Codecov (partial coverage) if: needs.changes.outputs.test_full_suite == 'false' - uses: codecov/codecov-action@v2.1.0 + uses: codecov/codecov-action@v3.1.0 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4770780341d..d3c30dc6506 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,6 +8,7 @@ on: jobs: stale: + if: github.repository_owner == 'home-assistant' runs-on: ubuntu-latest steps: # The 90 day stale policy @@ -16,7 +17,7 @@ jobs: # - No PRs marked as no-stale # - No issues marked as no-stale or help-wanted - name: 90 days stale issues & PRs policy - uses: actions/stale@v4 + uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 90 @@ -53,7 +54,7 @@ jobs: # - No PRs marked as no-stale or new-integrations # - No issues (-1) - name: 30 days stale PRs policy - uses: actions/stale@v4 + uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -78,7 +79,7 @@ jobs: # - No Issues marked as no-stale or help-wanted # - No PRs (-1) - name: Needs more information stale issues policy - uses: actions/stale@v4 + uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "needs-more-information" diff --git a/.github/workflows/translations.yaml b/.github/workflows/translations.yaml index 55273226b89..716ae870b50 100644 --- a/.github/workflows/translations.yaml +++ b/.github/workflows/translations.yaml @@ -21,10 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -40,10 +40,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v3.0.0 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ env.DEFAULT_PYTHON }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6c31b9003c1..950338cdd13 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -22,7 +22,7 @@ jobs: architectures: ${{ steps.info.outputs.architectures }} steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Get information id: info @@ -74,15 +74,15 @@ jobs: - "3.9-alpine3.14" steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Download env_file - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: env_file - name: Download requirements_diff - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: requirements_diff @@ -115,15 +115,15 @@ jobs: - "3.9-alpine3.14" steps: - name: Checkout the repository - uses: actions/checkout@v3.0.0 + uses: actions/checkout@v3.0.2 - name: Download env_file - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: env_file - name: Download requirements_diff - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: requirements_diff diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a32299a2224..4a012441a6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 + rev: v2.32.0 hooks: - id: pyupgrade args: [--py39-plus] diff --git a/.strict-typing b/.strict-typing index e808b54c85e..cda4f9e12fc 100644 --- a/.strict-typing +++ b/.strict-typing @@ -46,6 +46,7 @@ homeassistant.components.ambient_station.* homeassistant.components.amcrest.* homeassistant.components.ampio.* homeassistant.components.aseko_pool_live.* +homeassistant.components.asuswrt.* homeassistant.components.automation.* homeassistant.components.backup.* homeassistant.components.binary_sensor.* @@ -62,11 +63,7 @@ homeassistant.components.canary.* homeassistant.components.cover.* homeassistant.components.crownstone.* homeassistant.components.cpuspeed.* -homeassistant.components.deconz -homeassistant.components.deconz.config_flow -homeassistant.components.deconz.diagnostics -homeassistant.components.deconz.gateway -homeassistant.components.deconz.services +homeassistant.components.deconz.* homeassistant.components.device_automation.* homeassistant.components.device_tracker.* homeassistant.components.devolo_home_control.* @@ -78,6 +75,7 @@ homeassistant.components.dsmr.* homeassistant.components.dunehd.* homeassistant.components.efergy.* homeassistant.components.elgato.* +homeassistant.components.elkm1.* homeassistant.components.esphome.* homeassistant.components.energy.* homeassistant.components.evil_genius_labs.* @@ -88,6 +86,7 @@ homeassistant.components.flunearyou.* homeassistant.components.flux_led.* homeassistant.components.forecast_solar.* homeassistant.components.fritzbox.* +homeassistant.components.fritzbox_callmonitor.* homeassistant.components.fronius.* homeassistant.components.frontend.* homeassistant.components.fritz.* @@ -196,6 +195,7 @@ homeassistant.components.scene.* homeassistant.components.select.* homeassistant.components.sensor.* homeassistant.components.senseme.* +homeassistant.components.senz.* homeassistant.components.shelly.* homeassistant.components.simplisafe.* homeassistant.components.slack.* diff --git a/CODEOWNERS b/CODEOWNERS index 6da9c48adc9..c3405001f23 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -56,7 +56,6 @@ build.json @home-assistant/supervisor /tests/components/alexa/ @home-assistant/cloud @ochlocracy /homeassistant/components/almond/ @gcampax @balloob /tests/components/almond/ @gcampax @balloob -/homeassistant/components/alpha_vantage/ @fabaff /homeassistant/components/ambee/ @frenck /tests/components/ambee/ @frenck /homeassistant/components/amberelectric/ @madpilot @@ -82,7 +81,6 @@ build.json @home-assistant/supervisor /tests/components/aprs/ @PhilRW /homeassistant/components/arcam_fmj/ @elupus /tests/components/arcam_fmj/ @elupus -/homeassistant/components/arest/ @fabaff /homeassistant/components/arris_tg2492lg/ @vanbalken /homeassistant/components/aseko_pool_live/ @milanmeu /tests/components/aseko_pool_live/ @milanmeu @@ -121,7 +119,6 @@ build.json @home-assistant/supervisor /homeassistant/components/beewi_smartclim/ @alemuro /homeassistant/components/binary_sensor/ @home-assistant/core /tests/components/binary_sensor/ @home-assistant/core -/homeassistant/components/bitcoin/ @fabaff /homeassistant/components/bizkaibus/ @UgaitzEtxebarria /homeassistant/components/blebox/ @bbx-a @bbx-jp /tests/components/blebox/ @bbx-a @bbx-jp @@ -253,7 +250,6 @@ build.json @home-assistant/supervisor /homeassistant/components/dunehd/ @bieniu /tests/components/dunehd/ @bieniu /homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @Hummel95 -/homeassistant/components/dweet/ @fabaff /homeassistant/components/dynalite/ @ziv1234 /tests/components/dynalite/ @ziv1234 /homeassistant/components/eafm/ @Jc2k @@ -332,7 +328,6 @@ build.json @home-assistant/supervisor /tests/components/flipr/ @cnico /homeassistant/components/flo/ @dmulcahey /tests/components/flo/ @dmulcahey -/homeassistant/components/flock/ @fabaff /homeassistant/components/flume/ @ChrisMandich @bdraco /tests/components/flume/ @ChrisMandich @bdraco /homeassistant/components/flunearyou/ @bachya @@ -354,6 +349,8 @@ build.json @home-assistant/supervisor /tests/components/fritz/ @mammuth @AaronDavidSchneider @chemelli74 @mib1185 /homeassistant/components/fritzbox/ @mib1185 @flabbamann /tests/components/fritzbox/ @mib1185 @flabbamann +/homeassistant/components/fritzbox_callmonitor/ @cdce8p +/tests/components/fritzbox_callmonitor/ @cdce8p /homeassistant/components/fronius/ @nielstron @farmio /tests/components/fronius/ @nielstron @farmio /homeassistant/components/frontend/ @home-assistant/frontend @@ -381,9 +378,8 @@ build.json @home-assistant/supervisor /tests/components/gios/ @bieniu /homeassistant/components/github/ @timmo001 @ludeeus /tests/components/github/ @timmo001 @ludeeus -/homeassistant/components/gitter/ @fabaff -/homeassistant/components/glances/ @fabaff @engrbm87 -/tests/components/glances/ @fabaff @engrbm87 +/homeassistant/components/glances/ @engrbm87 +/tests/components/glances/ @engrbm87 /homeassistant/components/goalzero/ @tkdrob /tests/components/goalzero/ @tkdrob /homeassistant/components/gogogate2/ @vangorra @bdraco @@ -476,8 +472,8 @@ build.json @home-assistant/supervisor /homeassistant/components/image_processing/ @home-assistant/core /tests/components/image_processing/ @home-assistant/core /homeassistant/components/incomfort/ @zxdavb -/homeassistant/components/influxdb/ @fabaff @mdegat01 -/tests/components/influxdb/ @fabaff @mdegat01 +/homeassistant/components/influxdb/ @mdegat01 +/tests/components/influxdb/ @mdegat01 /homeassistant/components/input_boolean/ @home-assistant/core /tests/components/input_boolean/ @home-assistant/core /homeassistant/components/input_button/ @home-assistant/core @@ -554,6 +550,7 @@ build.json @home-assistant/supervisor /tests/components/lcn/ @alengwenus /homeassistant/components/lg_netcast/ @Drafteed /homeassistant/components/life360/ @pnbruckner +/homeassistant/components/lifx/ @Djelibeybi /homeassistant/components/light/ @home-assistant/core /tests/components/light/ @home-assistant/core /homeassistant/components/linux_battery/ @fabaff @@ -588,6 +585,8 @@ build.json @home-assistant/supervisor /homeassistant/components/matrix/ @tinloaf /homeassistant/components/mazda/ @bdr99 /tests/components/mazda/ @bdr99 +/homeassistant/components/meater/ @Sotolotl @emontnemery +/tests/components/meater/ @Sotolotl @emontnemery /homeassistant/components/media_player/ @home-assistant/core /tests/components/media_player/ @home-assistant/core /homeassistant/components/media_source/ @hunterjm @@ -637,7 +636,6 @@ build.json @home-assistant/supervisor /tests/components/motion_blinds/ @starkillerOG /homeassistant/components/motioneye/ @dermotduffy /tests/components/motioneye/ @dermotduffy -/homeassistant/components/mpd/ @fabaff /homeassistant/components/mqtt/ @emontnemery /tests/components/mqtt/ @emontnemery /homeassistant/components/msteams/ @peroyvind @@ -684,8 +682,6 @@ build.json @home-assistant/supervisor /tests/components/nina/ @DeerMaximum /homeassistant/components/nissan_leaf/ @filcole /homeassistant/components/nmbs/ @thibmaek -/homeassistant/components/no_ip/ @fabaff -/tests/components/no_ip/ @fabaff /homeassistant/components/noaa_tides/ @jdelaney72 /homeassistant/components/notify/ @home-assistant/core /tests/components/notify/ @home-assistant/core @@ -758,8 +754,8 @@ build.json @home-assistant/supervisor /tests/components/persistent_notification/ @home-assistant/core /homeassistant/components/philips_js/ @elupus /tests/components/philips_js/ @elupus -/homeassistant/components/pi_hole/ @fabaff @johnluetke @shenxn -/tests/components/pi_hole/ @fabaff @johnluetke @shenxn +/homeassistant/components/pi_hole/ @johnluetke @shenxn +/tests/components/pi_hole/ @johnluetke @shenxn /homeassistant/components/picnic/ @corneyl /tests/components/picnic/ @corneyl /homeassistant/components/pilight/ @trekky12 @@ -793,13 +789,15 @@ build.json @home-assistant/supervisor /tests/components/pure_energie/ @klaasnicolaas /homeassistant/components/push/ @dgomes /tests/components/push/ @dgomes -/homeassistant/components/pvoutput/ @fabaff @frenck -/tests/components/pvoutput/ @fabaff @frenck +/homeassistant/components/pvoutput/ @frenck +/tests/components/pvoutput/ @frenck /homeassistant/components/pvpc_hourly_pricing/ @azogue /tests/components/pvpc_hourly_pricing/ @azogue /homeassistant/components/qbittorrent/ @geoffreylagaisse /homeassistant/components/qld_bushfire/ @exxamalte /tests/components/qld_bushfire/ @exxamalte +/homeassistant/components/qnap_qsw/ @Noltari +/tests/components/qnap_qsw/ @Noltari /homeassistant/components/quantum_gateway/ @cisasteelersfan /homeassistant/components/qvr_pro/ @oblogic7 /homeassistant/components/qwikswitch/ @kellerza @@ -857,6 +855,8 @@ build.json @home-assistant/supervisor /tests/components/rtsp_to_webrtc/ @allenporter /homeassistant/components/ruckus_unleashed/ @gabe565 /tests/components/ruckus_unleashed/ @gabe565 +/homeassistant/components/sabnzbd/ @shaiu +/tests/components/sabnzbd/ @shaiu /homeassistant/components/safe_mode/ @home-assistant/core /tests/components/safe_mode/ @home-assistant/core /homeassistant/components/saj/ @fredericvl @@ -887,6 +887,8 @@ build.json @home-assistant/supervisor /tests/components/sensor/ @home-assistant/core /homeassistant/components/sentry/ @dcramer @frenck /tests/components/sentry/ @dcramer @frenck +/homeassistant/components/senz/ @milanmeu +/tests/components/senz/ @milanmeu /homeassistant/components/serial/ @fabaff /homeassistant/components/seven_segments/ @fabaff /homeassistant/components/sharkiq/ @JeffResc @funkybunch @AritroSaha10 @@ -915,6 +917,8 @@ build.json @home-assistant/supervisor /homeassistant/components/sleepiq/ @mfugate1 @kbickar /tests/components/sleepiq/ @mfugate1 @kbickar /homeassistant/components/slide/ @ualex73 +/homeassistant/components/slimproto/ @marcelveldt +/tests/components/slimproto/ @marcelveldt /homeassistant/components/sma/ @kellerza @rklomp /tests/components/sma/ @kellerza @rklomp /homeassistant/components/smappee/ @bsmappee @@ -929,8 +933,6 @@ build.json @home-assistant/supervisor /homeassistant/components/smhi/ @gjohansson-ST /tests/components/smhi/ @gjohansson-ST /homeassistant/components/sms/ @ocalvo -/homeassistant/components/smtp/ @fabaff -/tests/components/smtp/ @fabaff /homeassistant/components/solaredge/ @frenck /tests/components/solaredge/ @frenck /homeassistant/components/solaredge_local/ @drobtravels @scheric @@ -957,8 +959,8 @@ build.json @home-assistant/supervisor /homeassistant/components/splunk/ @Bre77 /homeassistant/components/spotify/ @frenck /tests/components/spotify/ @frenck -/homeassistant/components/sql/ @dgomes -/tests/components/sql/ @dgomes +/homeassistant/components/sql/ @dgomes @gjohansson-ST +/tests/components/sql/ @dgomes @gjohansson-ST /homeassistant/components/squeezebox/ @rajlaud /tests/components/squeezebox/ @rajlaud /homeassistant/components/srp_energy/ @briglx @@ -967,6 +969,8 @@ build.json @home-assistant/supervisor /tests/components/starline/ @anonym-tsk /homeassistant/components/statistics/ @fabaff @ThomDietrich /tests/components/statistics/ @fabaff @ThomDietrich +/homeassistant/components/steam_online/ @tkdrob +/tests/components/steam_online/ @tkdrob /homeassistant/components/steamist/ @bdraco /tests/components/steamist/ @bdraco /homeassistant/components/stiebel_eltron/ @fucm @@ -1002,7 +1006,6 @@ build.json @home-assistant/supervisor /homeassistant/components/synology_dsm/ @hacf-fr @Quentame @mib1185 /tests/components/synology_dsm/ @hacf-fr @Quentame @mib1185 /homeassistant/components/synology_srm/ @aerialls -/homeassistant/components/syslog/ @fabaff /homeassistant/components/system_bridge/ @timmo001 /tests/components/system_bridge/ @timmo001 /homeassistant/components/tado/ @michaelarnauts @north3221 @@ -1016,7 +1019,8 @@ build.json @home-assistant/supervisor /homeassistant/components/tapsaff/ @bazwilliams /homeassistant/components/tasmota/ @emontnemery /tests/components/tasmota/ @emontnemery -/homeassistant/components/tautulli/ @ludeeus +/homeassistant/components/tautulli/ @ludeeus @tkdrob +/tests/components/tautulli/ @ludeeus @tkdrob /homeassistant/components/tellduslive/ @fredrike /tests/components/tellduslive/ @fredrike /homeassistant/components/template/ @PhracturedBlue @tetienne @home-assistant/core @@ -1042,14 +1046,16 @@ build.json @home-assistant/supervisor /tests/components/tomorrowio/ @raman325 /homeassistant/components/totalconnect/ @austinmroczek /tests/components/totalconnect/ @austinmroczek -/homeassistant/components/tplink/ @rytilahti @thegardenmonkey -/tests/components/tplink/ @rytilahti @thegardenmonkey +/homeassistant/components/tplink/ @rytilahti @thegardenmonkey @bdraco +/tests/components/tplink/ @rytilahti @thegardenmonkey @bdraco /homeassistant/components/traccar/ @ludeeus /tests/components/traccar/ @ludeeus /homeassistant/components/trace/ @home-assistant/core /tests/components/trace/ @home-assistant/core /homeassistant/components/tractive/ @Danielhiversen @zhulik @bieniu /tests/components/tractive/ @Danielhiversen @zhulik @bieniu +/homeassistant/components/trafikverket_ferry/ @gjohansson-ST +/tests/components/trafikverket_ferry/ @gjohansson-ST /homeassistant/components/trafikverket_train/ @endor-force @gjohansson-ST /tests/components/trafikverket_train/ @endor-force @gjohansson-ST /homeassistant/components/trafikverket_weatherstation/ @endor-force @gjohansson-ST @@ -1058,8 +1064,8 @@ build.json @home-assistant/supervisor /tests/components/transmission/ @engrbm87 @JPHutchins /homeassistant/components/tts/ @pvizeli /tests/components/tts/ @pvizeli -/homeassistant/components/tuya/ @Tuya @zlinoliver @METISU @frenck -/tests/components/tuya/ @Tuya @zlinoliver @METISU @frenck +/homeassistant/components/tuya/ @Tuya @zlinoliver @frenck +/tests/components/tuya/ @Tuya @zlinoliver @frenck /homeassistant/components/twentemilieu/ @frenck /tests/components/twentemilieu/ @frenck /homeassistant/components/twinkly/ @dr1rrb @Robbie1221 @@ -1076,8 +1082,6 @@ build.json @home-assistant/supervisor /tests/components/upcloud/ @scop /homeassistant/components/update/ @home-assistant/core /tests/components/update/ @home-assistant/core -/homeassistant/components/updater/ @home-assistant/core -/tests/components/updater/ @home-assistant/core /homeassistant/components/upnp/ @StevenLooman @ehendrix23 /tests/components/upnp/ @StevenLooman @ehendrix23 /homeassistant/components/uptime/ @frenck @@ -1104,8 +1108,8 @@ build.json @home-assistant/supervisor /homeassistant/components/verisure/ @frenck /tests/components/verisure/ @frenck /homeassistant/components/versasense/ @flamm3blemuff1n -/homeassistant/components/version/ @fabaff @ludeeus -/tests/components/version/ @fabaff @ludeeus +/homeassistant/components/version/ @ludeeus +/tests/components/version/ @ludeeus /homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey /tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey /homeassistant/components/vicare/ @oischinger diff --git a/Dockerfile.dev b/Dockerfile.dev index 39ce36074ad..dc04efe56fb 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -19,6 +19,7 @@ RUN \ libpcap-dev \ libturbojpeg0 \ git \ + cmake \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index a4797dd8c10..3971cb43080 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -147,9 +147,7 @@ class Data: if not bcrypt.checkpw(password.encode(), user_hash): raise InvalidAuth - def hash_password( # pylint: disable=no-self-use - self, password: str, for_storage: bool = False - ) -> bytes: + def hash_password(self, password: str, for_storage: bool = False) -> bytes: """Encode a password.""" hashed: bytes = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)) diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index ca7f64ce28d..c09a8ebd811 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -4,10 +4,7 @@ from __future__ import annotations from abodepy.devices.alarm import AbodeAlarm as AbodeAl import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -38,7 +35,10 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity): _attr_icon = ICON _attr_code_arm_required = False - _attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) _device: AbodeAl @property diff --git a/homeassistant/components/accuweather/translations/ca.json b/homeassistant/components/accuweather/translations/ca.json index 8178a5caef0..feda485d8fd 100644 --- a/homeassistant/components/accuweather/translations/ca.json +++ b/homeassistant/components/accuweather/translations/ca.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, + "create_entry": { + "default": "Alguns sensors no estan activats de manera predeterminada. Els pots activar des del registre d'entitats, despr\u00e9s de la configuraci\u00f3 de la integraci\u00f3.\nLa previsi\u00f3 meteorol\u00f2gica no est\u00e0 activada de manera predeterminada. Pots activar-la a les opcions de la integraci\u00f3." + }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_api_key": "Clau API inv\u00e0lida", diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json index 17eb0ee31fc..ac0c430de04 100644 --- a/homeassistant/components/accuweather/translations/de.json +++ b/homeassistant/components/accuweather/translations/de.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "create_entry": { + "default": "Einige Sensoren sind standardm\u00e4\u00dfig nicht aktiviert. Du kannst sie nach der Integrationskonfiguration in der Entit\u00e4tsregistrierung aktivieren.\nDie Wettervorhersage ist nicht standardm\u00e4\u00dfig aktiviert. Du kannst sie in den Integrationsoptionen aktivieren." + }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", diff --git a/homeassistant/components/accuweather/translations/el.json b/homeassistant/components/accuweather/translations/el.json index 4f7a23e1d6f..43a65158455 100644 --- a/homeassistant/components/accuweather/translations/el.json +++ b/homeassistant/components/accuweather/translations/el.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, + "create_entry": { + "default": "\u039f\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03b7\u03c4\u03c1\u03ce\u03bf \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7\u03c2.\n \u0397 \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2." + }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", diff --git a/homeassistant/components/accuweather/translations/en.json b/homeassistant/components/accuweather/translations/en.json index 8f2261b93c7..a984080fd86 100644 --- a/homeassistant/components/accuweather/translations/en.json +++ b/homeassistant/components/accuweather/translations/en.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Already configured. Only a single configuration possible." }, + "create_entry": { + "default": "Some sensors are not enabled by default. You can enable them in the entity registry after the integration configuration.\nWeather forecast is not enabled by default. You can enable it in the integration options." + }, "error": { "cannot_connect": "Failed to connect", "invalid_api_key": "Invalid API key", diff --git a/homeassistant/components/accuweather/translations/et.json b/homeassistant/components/accuweather/translations/et.json index 6e2dc1ffd96..429e2dec61e 100644 --- a/homeassistant/components/accuweather/translations/et.json +++ b/homeassistant/components/accuweather/translations/et.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Sidumine juba tehtud. V\u00f5imalik on ainult 1 sidumine." }, + "create_entry": { + "default": "M\u00f5ned andurid ei ole vaikimisi lubatud. Saad neid lubada \u00fcksuse registris p\u00e4rast sidumise seadistamist.\nIlmaprognoos ei ole vaikimisi lubatud. Saad selle lubada sidumise valikutes." + }, "error": { "cannot_connect": "\u00dchendamine nurjus", "invalid_api_key": "API v\u00f5ti on vale", diff --git a/homeassistant/components/accuweather/translations/fr.json b/homeassistant/components/accuweather/translations/fr.json index 1de45f8da22..cf407ef3962 100644 --- a/homeassistant/components/accuweather/translations/fr.json +++ b/homeassistant/components/accuweather/translations/fr.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, + "create_entry": { + "default": "Certains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s une fois la configuration de l'int\u00e9gration termin\u00e9e.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez les activer dans les options de l'int\u00e9gration." + }, "error": { "cannot_connect": "\u00c9chec de connexion", "invalid_api_key": "Cl\u00e9 d'API non valide", @@ -16,7 +19,7 @@ "longitude": "Longitude", "name": "Nom" }, - "description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration.", + "description": "Si vous avez besoin d'aide pour la configuration, consultez\u00a0: https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s une fois la configuration de l'int\u00e9gration termin\u00e9e.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez les activer dans les options de l'int\u00e9gration.", "title": "AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/hu.json b/homeassistant/components/accuweather/translations/hu.json index 8b0409d1f22..1b30dd09daf 100644 --- a/homeassistant/components/accuweather/translations/hu.json +++ b/homeassistant/components/accuweather/translations/hu.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, + "create_entry": { + "default": "Egyes \u00e9rz\u00e9kel\u0151k alap\u00e9rtelmez\u00e9s szerint nincsenek enged\u00e9lyezve. Az integr\u00e1ci\u00f3s konfigur\u00e1ci\u00f3 ut\u00e1n enged\u00e9lyezheti \u0151ket az entit\u00e1s rendszerle\u00edr\u00f3 adatb\u00e1zis\u00e1ban.\nAz id\u0151j\u00e1r\u00e1s-el\u0151rejelz\u00e9s alap\u00e9rtelmez\u00e9s szerint nincs enged\u00e9lyezve. Ezt az integr\u00e1ci\u00f3s be\u00e1ll\u00edt\u00e1sokban enged\u00e9lyezheti." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", @@ -14,7 +17,7 @@ "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Ha seg\u00edts\u00e9gre van sz\u00fcks\u00e9ge a konfigur\u00e1l\u00e1shoz, n\u00e9zze meg itt: https://www.home-assistant.io/integrations/accuweather/ \n\nEgyes \u00e9rz\u00e9kel\u0151k alap\u00e9rtelmez\u00e9s szerint nincsenek enged\u00e9lyezve. Az integr\u00e1ci\u00f3s konfigur\u00e1ci\u00f3 ut\u00e1n enged\u00e9lyezheti \u0151ket az entit\u00e1s-nyilv\u00e1ntart\u00e1sban.\nAz id\u0151j\u00e1r\u00e1s-el\u0151rejelz\u00e9s alap\u00e9rtelmez\u00e9s szerint nincs enged\u00e9lyezve. Ezt az integr\u00e1ci\u00f3s be\u00e1ll\u00edt\u00e1sokban enged\u00e9lyezheti.", "title": "AccuWeather" diff --git a/homeassistant/components/accuweather/translations/id.json b/homeassistant/components/accuweather/translations/id.json index 970b3a026b7..7bf9f27e8b2 100644 --- a/homeassistant/components/accuweather/translations/id.json +++ b/homeassistant/components/accuweather/translations/id.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, + "create_entry": { + "default": "Beberapa sensor tidak diaktifkan secara default. Anda dapat mengaktifkannya di registri entitas setelah konfigurasi integrasi.\nPrakiraan cuaca tidak diaktifkan secara default. Anda dapat mengaktifkannya di opsi integrasi." + }, "error": { "cannot_connect": "Gagal terhubung", "invalid_api_key": "Kunci API tidak valid", diff --git a/homeassistant/components/accuweather/translations/it.json b/homeassistant/components/accuweather/translations/it.json index 8a1f9b96463..9daaf378fb4 100644 --- a/homeassistant/components/accuweather/translations/it.json +++ b/homeassistant/components/accuweather/translations/it.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, + "create_entry": { + "default": "Alcuni sensori non sono abilitati per impostazione predefinita. Puoi abilitarli nel registro delle entit\u00e0 dopo la configurazione dell'integrazione.\nLe previsioni del tempo non sono abilitate per impostazione predefinita. Puoi abilitarlo nelle opzioni di integrazione." + }, "error": { "cannot_connect": "Impossibile connettersi", "invalid_api_key": "Chiave API non valida", diff --git a/homeassistant/components/accuweather/translations/ja.json b/homeassistant/components/accuweather/translations/ja.json index 10fe398ba04..d05e75e74b5 100644 --- a/homeassistant/components/accuweather/translations/ja.json +++ b/homeassistant/components/accuweather/translations/ja.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" }, + "create_entry": { + "default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002" + }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc", diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index f04d93b5921..2a6dd1a812b 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, + "create_entry": { + "default": "Sommige sensoren zijn standaard niet ingeschakeld. U kunt ze inschakelen in het entiteitenregister na de integratieconfiguratie.\nWeersvoorspelling is niet standaard ingeschakeld. U kunt deze inschakelen in de integratieopties." + }, "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "API-sleutel", diff --git a/homeassistant/components/accuweather/translations/no.json b/homeassistant/components/accuweather/translations/no.json index be87b1ab244..af689b2fd1c 100644 --- a/homeassistant/components/accuweather/translations/no.json +++ b/homeassistant/components/accuweather/translations/no.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, + "create_entry": { + "default": "Noen sensorer er ikke aktivert som standard. Du kan aktivere dem i enhetsregisteret etter integrasjonskonfigurasjonen.\n V\u00e6rmelding er ikke aktivert som standard. Du kan aktivere det i integreringsalternativene." + }, "error": { "cannot_connect": "Tilkobling mislyktes", "invalid_api_key": "Ugyldig API-n\u00f8kkel", diff --git a/homeassistant/components/accuweather/translations/pl.json b/homeassistant/components/accuweather/translations/pl.json index 2794bc8b7b6..a8ef8a3bfa0 100644 --- a/homeassistant/components/accuweather/translations/pl.json +++ b/homeassistant/components/accuweather/translations/pl.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, + "create_entry": { + "default": "Niekt\u00f3re sensory nie s\u0105 domy\u015blnie w\u0142\u0105czone. Mo\u017cesz je w\u0142\u0105czy\u0107 w rejestrze encji po skonfigurowaniu integracji.\nPrognoza pogody nie jest domy\u015blnie w\u0142\u0105czona. Mo\u017cesz to w\u0142\u0105czy\u0107 w opcjach integracji." + }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_api_key": "Nieprawid\u0142owy klucz API", diff --git a/homeassistant/components/accuweather/translations/pt-BR.json b/homeassistant/components/accuweather/translations/pt-BR.json index 469dd81ff91..4bb1bbbc97e 100644 --- a/homeassistant/components/accuweather/translations/pt-BR.json +++ b/homeassistant/components/accuweather/translations/pt-BR.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, + "create_entry": { + "default": "Alguns sensores n\u00e3o s\u00e3o ativados por padr\u00e3o. Voc\u00ea pode habilit\u00e1-los no registro da entidade ap\u00f3s a configura\u00e7\u00e3o da integra\u00e7\u00e3o.\n A previs\u00e3o do tempo n\u00e3o est\u00e1 habilitada por padr\u00e3o. Voc\u00ea pode habilit\u00e1-lo nas op\u00e7\u00f5es de integra\u00e7\u00e3o." + }, "error": { "cannot_connect": "Falha ao conectar", "invalid_api_key": "Chave de API inv\u00e1lida", diff --git a/homeassistant/components/accuweather/translations/ru.json b/homeassistant/components/accuweather/translations/ru.json index 7bc767b1baf..dfd24b6f147 100644 --- a/homeassistant/components/accuweather/translations/ru.json +++ b/homeassistant/components/accuweather/translations/ru.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, + "create_entry": { + "default": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u044b \u0441\u043a\u0440\u044b\u0442\u044b \u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0443\u0436\u043d\u044b\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0438 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438." + }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", diff --git a/homeassistant/components/accuweather/translations/tr.json b/homeassistant/components/accuweather/translations/tr.json index 7b0fa476458..b5efeb7080e 100644 --- a/homeassistant/components/accuweather/translations/tr.json +++ b/homeassistant/components/accuweather/translations/tr.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, + "create_entry": { + "default": "Baz\u0131 sens\u00f6rler varsay\u0131lan olarak etkin de\u011fildir. Bunlar\u0131, entegrasyon yap\u0131land\u0131rmas\u0131ndan sonra varl\u0131k kay\u0131t defterinde etkinle\u015ftirebilirsiniz.\n Hava tahmini varsay\u0131lan olarak etkin de\u011fildir. Entegrasyon se\u00e7eneklerinde etkinle\u015ftirebilirsiniz." + }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", diff --git a/homeassistant/components/accuweather/translations/zh-Hant.json b/homeassistant/components/accuweather/translations/zh-Hant.json index fbf72991e92..6d9e7e1c36f 100644 --- a/homeassistant/components/accuweather/translations/zh-Hant.json +++ b/homeassistant/components/accuweather/translations/zh-Hant.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, + "create_entry": { + "default": "\u90e8\u5206\u611f\u6e2c\u5668\u9810\u8a2d\u70ba\u4e0d\u555f\u7528\u72c0\u614b\u3002\u53ef\u4ee5\u7a0d\u5f8c\u65bc\u6574\u5408\u8a2d\u5b9a\u9801\u9762\u4e2d\u7684\u5be6\u9ad4\u8a3b\u518a\u8868\u9032\u884c\u555f\u7528\u3002\n\u5929\u6c23\u9810\u5831\u9810\u8a2d\u70ba\u4e0d\u555f\u7528\u3001\u53ef\u4ee5\u65bc\u6574\u5408\u9078\u9805\u4e2d\u9032\u884c\u555f\u7528\u3002" + }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_api_key": "API \u91d1\u9470\u7121\u6548", diff --git a/homeassistant/components/acmeda/cover.py b/homeassistant/components/acmeda/cover.py index 8c934e51771..2fbd2de6c42 100644 --- a/homeassistant/components/acmeda/cover.py +++ b/homeassistant/components/acmeda/cover.py @@ -3,15 +3,8 @@ from __future__ import annotations from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -79,14 +72,17 @@ class AcmedaCover(AcmedaBase, CoverEntity): supported_features = 0 if self.current_cover_position is not None: supported_features |= ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION ) if self.current_cover_tilt_position is not None: supported_features |= ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_STOP_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION ) return supported_features diff --git a/homeassistant/components/adax/climate.py b/homeassistant/components/adax/climate.py index ea8e3554746..258e50f1047 100644 --- a/homeassistant/components/adax/climate.py +++ b/homeassistant/components/adax/climate.py @@ -7,11 +7,7 @@ from adax import Adax from adax_local import Adax as AdaxLocal from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_TEMPERATURE, @@ -65,10 +61,10 @@ async def async_setup_entry( class AdaxDevice(ClimateEntity): """Representation of a heater.""" - _attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF] + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] _attr_max_temp = 35 _attr_min_temp = 5 - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_target_temperature_step = PRECISION_WHOLE _attr_temperature_unit = TEMP_CELSIUS @@ -86,12 +82,12 @@ class AdaxDevice(ClimateEntity): async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set hvac mode.""" - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: temperature = max(self.min_temp, self.target_temperature or self.min_temp) await self._adax_data_handler.set_room_target_temperature( self._device_id, temperature, True ) - elif hvac_mode == HVAC_MODE_OFF: + elif hvac_mode == HVACMode.OFF: await self._adax_data_handler.set_room_target_temperature( self._device_id, self.min_temp, False ) @@ -116,10 +112,10 @@ class AdaxDevice(ClimateEntity): self._attr_current_temperature = room.get("temperature") self._attr_target_temperature = room.get("targetTemperature") if room["heatingEnabled"]: - self._attr_hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_mode = HVACMode.HEAT self._attr_icon = "mdi:radiator" else: - self._attr_hvac_mode = HVAC_MODE_OFF + self._attr_hvac_mode = HVACMode.OFF self._attr_icon = "mdi:radiator-off" return @@ -127,11 +123,11 @@ class AdaxDevice(ClimateEntity): class LocalAdaxDevice(ClimateEntity): """Representation of a heater.""" - _attr_hvac_modes = [HVAC_MODE_HEAT] - _attr_hvac_mode = HVAC_MODE_HEAT + _attr_hvac_modes = [HVACMode.HEAT] + _attr_hvac_mode = HVACMode.HEAT _attr_max_temp = 35 _attr_min_temp = 5 - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_target_temperature_step = PRECISION_WHOLE _attr_temperature_unit = TEMP_CELSIUS diff --git a/homeassistant/components/adax/translations/hu.json b/homeassistant/components/adax/translations/hu.json index 9f37837420f..a2a6dcdacaf 100644 --- a/homeassistant/components/adax/translations/hu.json +++ b/homeassistant/components/adax/translations/hu.json @@ -20,9 +20,9 @@ "local": { "data": { "wifi_pswd": "WiFi jelsz\u00f3", - "wifi_ssid": "WiFi ssid" + "wifi_ssid": "Wi-Fi SSID" }, - "description": "\u00c1ll\u00edtsa vissza a f\u0171t\u0151berendez\u00e9st a + \u00e9s az OK gomb nyomvatart\u00e1s\u00e1val, m\u00edg a kijelz\u0151n a \"Reset\" (Vissza\u00e1ll\u00edt\u00e1s) felirat nem jelenik meg. Ezut\u00e1n nyomja meg \u00e9s tartsa lenyomva a f\u0171t\u0151berendez\u00e9s OK gombj\u00e1t, am\u00edg a k\u00e9k led villogni nem kezd, majd nyomja meg a K\u00fcld\u00e9s gombot. A f\u0171t\u0151berendez\u00e9s konfigur\u00e1l\u00e1sa n\u00e9h\u00e1ny percet vehet ig\u00e9nybe." + "description": "\u00c1ll\u00edtsa vissza a f\u0171t\u0151berendez\u00e9st a + \u00e9s az OK gomb nyomvatart\u00e1s\u00e1val, m\u00edg a kijelz\u0151n a \"Reset\" (Vissza\u00e1ll\u00edt\u00e1s) felirat nem jelenik meg. Ezut\u00e1n nyomja meg \u00e9s tartsa lenyomva a f\u0171t\u0151berendez\u00e9s OK gombj\u00e1t, am\u00edg a k\u00e9k led villogni nem kezd, l\u00e9pjen tov\u00e1bb. A f\u0171t\u0151berendez\u00e9s konfigur\u00e1l\u00e1sa n\u00e9h\u00e1ny percet vehet ig\u00e9nybe." }, "user": { "data": { diff --git a/homeassistant/components/adax/translations/nl.json b/homeassistant/components/adax/translations/nl.json index 026c3c06dae..ced3b8bdab0 100644 --- a/homeassistant/components/adax/translations/nl.json +++ b/homeassistant/components/adax/translations/nl.json @@ -31,7 +31,7 @@ "host": "Host", "password": "Wachtwoord" }, - "description": "Selecteer verbindingstype. Lokaal vereist verwarming met bluetooth" + "description": "Selecteer verbindingstype. Lokaal vereist verwarming met Bluetooth." } } } diff --git a/homeassistant/components/ads/cover.py b/homeassistant/components/ads/cover.py index 187758aa8b1..c1f057b588e 100644 --- a/homeassistant/components/ads/cover.py +++ b/homeassistant/components/ads/cover.py @@ -8,11 +8,8 @@ from homeassistant.components.cover import ( ATTR_POSITION, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverEntity, + CoverEntityFeature, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME from homeassistant.core import HomeAssistant @@ -117,11 +114,13 @@ class AdsCover(AdsEntity, CoverEntity): self._ads_var_close = ads_var_close self._ads_var_stop = ads_var_stop self._attr_device_class = device_class - self._attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + self._attr_supported_features = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + ) if ads_var_stop is not None: - self._attr_supported_features |= SUPPORT_STOP + self._attr_supported_features |= CoverEntityFeature.STOP if ads_var_pos_set is not None: - self._attr_supported_features |= SUPPORT_SET_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_POSITION async def async_added_to_hass(self): """Register device notification.""" diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index a6bccc68fe5..a4ec970ee15 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_NAME @@ -60,7 +60,11 @@ class AdsLight(AdsEntity, LightEntity): self._state_dict[STATE_KEY_BRIGHTNESS] = None self._ads_var_brightness = ads_var_brightness if ads_var_brightness is not None: - self._attr_supported_features = SUPPORT_BRIGHTNESS + self._attr_color_mode = ColorMode.BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + else: + self._attr_color_mode = ColorMode.ONOFF + self._attr_supported_color_modes = {ColorMode.ONOFF} async def async_added_to_hass(self): """Register device notification.""" diff --git a/homeassistant/components/advantage_air/climate.py b/homeassistant/components/advantage_air/climate.py index ea31ae71942..192e1987902 100644 --- a/homeassistant/components/advantage_air/climate.py +++ b/homeassistant/components/advantage_air/climate.py @@ -9,15 +9,8 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS @@ -35,20 +28,20 @@ from .const import ( from .entity import AdvantageAirEntity ADVANTAGE_AIR_HVAC_MODES = { - "heat": HVAC_MODE_HEAT, - "cool": HVAC_MODE_COOL, - "vent": HVAC_MODE_FAN_ONLY, - "dry": HVAC_MODE_DRY, - "myauto": HVAC_MODE_AUTO, + "heat": HVACMode.HEAT, + "cool": HVACMode.COOL, + "vent": HVACMode.FAN_ONLY, + "dry": HVACMode.DRY, + "myauto": HVACMode.AUTO, } HASS_HVAC_MODES = {v: k for k, v in ADVANTAGE_AIR_HVAC_MODES.items()} AC_HVAC_MODES = [ - HVAC_MODE_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_DRY, + HVACMode.OFF, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + HVACMode.DRY, ] ADVANTAGE_AIR_FAN_MODES = { @@ -61,7 +54,7 @@ HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()} FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100} ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone" -ZONE_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL] +ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL] PARALLEL_UPDATES = 0 @@ -108,7 +101,9 @@ class AdvantageAirAC(AdvantageAirClimateEntity): _attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH] _attr_hvac_modes = AC_HVAC_MODES - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) def __init__(self, instance, ac_key): """Initialize an AdvantageAir AC unit.""" @@ -116,7 +111,7 @@ class AdvantageAirAC(AdvantageAirClimateEntity): self._attr_name = self._ac["name"] self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{ac_key}' if self._ac.get("myAutoModeEnabled"): - self._attr_hvac_modes = AC_HVAC_MODES + [HVAC_MODE_AUTO] + self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO] @property def target_temperature(self): @@ -128,7 +123,7 @@ class AdvantageAirAC(AdvantageAirClimateEntity): """Return the current HVAC modes.""" if self._ac["state"] == ADVANTAGE_AIR_STATE_ON: return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"]) - return HVAC_MODE_OFF + return HVACMode.OFF @property def fan_mode(self): @@ -137,7 +132,7 @@ class AdvantageAirAC(AdvantageAirClimateEntity): async def async_set_hvac_mode(self, hvac_mode): """Set the HVAC Mode and State.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_change( {self.ac_key: {"info": {"state": ADVANTAGE_AIR_STATE_OFF}}} ) @@ -169,7 +164,7 @@ class AdvantageAirZone(AdvantageAirClimateEntity): """AdvantageAir Zone control.""" _attr_hvac_modes = ZONE_HVAC_MODES - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE def __init__(self, instance, ac_key, zone_key): """Initialize an AdvantageAir Zone control.""" @@ -183,8 +178,8 @@ class AdvantageAirZone(AdvantageAirClimateEntity): def hvac_mode(self): """Return the current state as HVAC mode.""" if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: - return HVAC_MODE_HEAT_COOL - return HVAC_MODE_OFF + return HVACMode.HEAT_COOL + return HVACMode.OFF @property def current_temperature(self): @@ -198,7 +193,7 @@ class AdvantageAirZone(AdvantageAirClimateEntity): async def async_set_hvac_mode(self, hvac_mode): """Set the HVAC Mode and State.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_change( { self.ac_key: { diff --git a/homeassistant/components/advantage_air/cover.py b/homeassistant/components/advantage_air/cover.py index a54d6d8b535..847ca41c42c 100644 --- a/homeassistant/components/advantage_air/cover.py +++ b/homeassistant/components/advantage_air/cover.py @@ -1,11 +1,9 @@ """Cover platform for Advantage Air integration.""" from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -43,7 +41,11 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity): """Advantage Air Cover Class.""" _attr_device_class = CoverDeviceClass.DAMPER - _attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) def __init__(self, instance, ac_key, zone_key): """Initialize an Advantage Air Cover Class.""" diff --git a/homeassistant/components/aemet/translations/ca.json b/homeassistant/components/aemet/translations/ca.json index 75784ddfc87..7dab53fc049 100644 --- a/homeassistant/components/aemet/translations/ca.json +++ b/homeassistant/components/aemet/translations/ca.json @@ -14,7 +14,7 @@ "longitude": "Longitud", "name": "Nom de la integraci\u00f3" }, - "description": "Configura la integraci\u00f3 d'AEMET OpenData. Per generar la clau API, v\u00e9s a https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Per generar la clau API, v\u00e9s a https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json index 0704e7d71ba..25f374f2e43 100644 --- a/homeassistant/components/aemet/translations/de.json +++ b/homeassistant/components/aemet/translations/de.json @@ -14,7 +14,7 @@ "longitude": "L\u00e4ngengrad", "name": "Name der Integration" }, - "description": "Richte die AEMET OpenData Integration ein. Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/en.json b/homeassistant/components/aemet/translations/en.json index 3888ccdafc0..7c10d1dc676 100644 --- a/homeassistant/components/aemet/translations/en.json +++ b/homeassistant/components/aemet/translations/en.json @@ -14,7 +14,7 @@ "longitude": "Longitude", "name": "Name of the integration" }, - "description": "Set up AEMET OpenData integration. To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/et.json b/homeassistant/components/aemet/translations/et.json index 0d542fcc744..04292b3d912 100644 --- a/homeassistant/components/aemet/translations/et.json +++ b/homeassistant/components/aemet/translations/et.json @@ -14,7 +14,7 @@ "longitude": "Pikkuskraad", "name": "Sidumise nimi" }, - "description": "Seadista AEMET OpenData sidumine. API v\u00f5tme loomiseks mine aadressile https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "API-v\u00f5tme loomiseks mine aadressile https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/fr.json b/homeassistant/components/aemet/translations/fr.json index 0d3d0e77a5e..bddfd2507bf 100644 --- a/homeassistant/components/aemet/translations/fr.json +++ b/homeassistant/components/aemet/translations/fr.json @@ -14,7 +14,7 @@ "longitude": "Longitude", "name": "Nom de l'int\u00e9gration" }, - "description": "Configurez l'int\u00e9gration AEMET OpenData. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/hu.json b/homeassistant/components/aemet/translations/hu.json index 31a7654efd9..2f2984b1c90 100644 --- a/homeassistant/components/aemet/translations/hu.json +++ b/homeassistant/components/aemet/translations/hu.json @@ -14,7 +14,7 @@ "longitude": "Hossz\u00fas\u00e1g", "name": "Az integr\u00e1ci\u00f3 neve" }, - "description": "\u00c1ll\u00edtsa be az AEMET OpenData integr\u00e1ci\u00f3t. Az API-kulcs el\u0151\u00e1ll\u00edt\u00e1s\u00e1hoz keresse fel a https://opendata.aemet.es/centrodedescargas/altaUsuario webhelyet.", + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://opendata.aemet.es/centrodedescargas/altaUsuario webhelyet", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/id.json b/homeassistant/components/aemet/translations/id.json index e3a602a9a7c..62239172aae 100644 --- a/homeassistant/components/aemet/translations/id.json +++ b/homeassistant/components/aemet/translations/id.json @@ -14,7 +14,7 @@ "longitude": "Bujur", "name": "Nama integrasi" }, - "description": "Siapkan integrasi AEMET OpenData. Untuk menghasilkan kunci API, buka https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Untuk membuat kunci API, buka https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/it.json b/homeassistant/components/aemet/translations/it.json index a55e003ca4e..4b3f6f2251b 100644 --- a/homeassistant/components/aemet/translations/it.json +++ b/homeassistant/components/aemet/translations/it.json @@ -14,7 +14,7 @@ "longitude": "Logitudine", "name": "Nome dell'integrazione" }, - "description": "Imposta l'integrazione di AEMET OpenData. Per generare la chiave API, vai su https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Per generare la chiave API, vai su https://opendata.aemet.es/centrodescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/nl.json b/homeassistant/components/aemet/translations/nl.json index 40fab5d9e0f..abf590e5a36 100644 --- a/homeassistant/components/aemet/translations/nl.json +++ b/homeassistant/components/aemet/translations/nl.json @@ -14,7 +14,7 @@ "longitude": "Lengtegraad", "name": "Naam van de integratie" }, - "description": "Stel AEMET OpenData-integratie in. Ga naar https://opendata.aemet.es/centrodedescargas/altaUsuario om een API-sleutel te genereren", + "description": "Om een API sleutel te genereren ga naar https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/no.json b/homeassistant/components/aemet/translations/no.json index fe36ff835ee..a6076844122 100644 --- a/homeassistant/components/aemet/translations/no.json +++ b/homeassistant/components/aemet/translations/no.json @@ -14,7 +14,7 @@ "longitude": "Lengdegrad", "name": "Navnet p\u00e5 integrasjonen" }, - "description": "Sett opp AEMET OpenData-integrasjon. For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/pl.json b/homeassistant/components/aemet/translations/pl.json index 8531ca47fd6..f1021585140 100644 --- a/homeassistant/components/aemet/translations/pl.json +++ b/homeassistant/components/aemet/translations/pl.json @@ -14,7 +14,7 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "name": "Nazwa integracji" }, - "description": "Skonfiguruj integracj\u0119 AEMET OpenData. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/pt-BR.json b/homeassistant/components/aemet/translations/pt-BR.json index 6a0c8800b02..7c5972dccd8 100644 --- a/homeassistant/components/aemet/translations/pt-BR.json +++ b/homeassistant/components/aemet/translations/pt-BR.json @@ -14,7 +14,7 @@ "longitude": "Longitude", "name": "Nome da integra\u00e7\u00e3o" }, - "description": "Configure a integra\u00e7\u00e3o AEMET OpenData. Para gerar a chave API acesse https://opendata.aemet.es/centrodedescargas/altaUsuario", + "description": "Para gerar a chave API acesse https://opendata.aemet.es/centrodedescargas/altaUsuario", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/ru.json b/homeassistant/components/aemet/translations/ru.json index f9278af712b..9d8496cbb2d 100644 --- a/homeassistant/components/aemet/translations/ru.json +++ b/homeassistant/components/aemet/translations/ru.json @@ -14,7 +14,7 @@ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AEMET OpenData. \u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://opendata.aemet.es/centrodedescargas/altaUsuario.", + "description": "\u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://opendata.aemet.es/centrodedescargas/altaUsuario.", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/tr.json b/homeassistant/components/aemet/translations/tr.json index 7cb0048b0e0..e1e3cae01ff 100644 --- a/homeassistant/components/aemet/translations/tr.json +++ b/homeassistant/components/aemet/translations/tr.json @@ -14,7 +14,7 @@ "longitude": "Boylam", "name": "Entegrasyonun ad\u0131" }, - "description": "AEMET OpenData entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://opendata.aemet.es/centrodedescargas/altaUsuario adresine gidin.", + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://opendata.aemet.es/centrodedescargas/altaUsuario adresine gidin.", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/zh-Hant.json b/homeassistant/components/aemet/translations/zh-Hant.json index e064a6c0192..4d77ca17505 100644 --- a/homeassistant/components/aemet/translations/zh-Hant.json +++ b/homeassistant/components/aemet/translations/zh-Hant.json @@ -14,7 +14,7 @@ "longitude": "\u7d93\u5ea6", "name": "\u6574\u5408\u540d\u7a31" }, - "description": "\u6b32\u8a2d\u5b9a AEMET OpenData \u6574\u5408\u3002\u8acb\u81f3 https://opendata.aemet.es/centrodedescargas/altaUsuario \u7522\u751f API \u91d1\u9470", + "description": "\u8acb\u81f3 https://opendata.aemet.es/centrodedescargas/altaUsuario \u4ee5\u7522\u751f API \u91d1\u9470", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/agent_dvr/alarm_control_panel.py b/homeassistant/components/agent_dvr/alarm_control_panel.py index 572f80a138f..3e264a1985d 100644 --- a/homeassistant/components/agent_dvr/alarm_control_panel.py +++ b/homeassistant/components/agent_dvr/alarm_control_panel.py @@ -1,9 +1,7 @@ """Support for Agent DVR Alarm Control Panels.""" -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -38,7 +36,9 @@ class AgentBaseStation(AlarmControlPanelEntity): _attr_icon = ICON _attr_supported_features = ( - SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT ) def __init__(self, client): diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index e82bbeaea1b..49ca342ca09 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -4,7 +4,7 @@ import logging from agent import AgentError -from homeassistant.components.camera import SUPPORT_ON_OFF +from homeassistant.components.camera import CameraEntityFeature from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers import entity_platform @@ -63,6 +63,8 @@ async def async_setup_entry( class AgentCamera(MjpegCamera): """Representation of an Agent Device Stream.""" + _attr_supported_features = CameraEntityFeature.ON_OFF + def __init__(self, device): """Initialize as a subclass of MjpegCamera.""" self.device = device @@ -134,11 +136,6 @@ class AgentCamera(MjpegCamera): """Return True if entity is connected.""" return self.device.connected - @property - def supported_features(self) -> int: - """Return supported features.""" - return SUPPORT_ON_OFF - @property def is_on(self) -> bool: """Return true if on.""" diff --git a/homeassistant/components/agent_dvr/translations/hu.json b/homeassistant/components/agent_dvr/translations/hu.json index 83751d72eaf..fdddd0ba92e 100644 --- a/homeassistant/components/agent_dvr/translations/hu.json +++ b/homeassistant/components/agent_dvr/translations/hu.json @@ -4,7 +4,7 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { diff --git a/homeassistant/components/airly/diagnostics.py b/homeassistant/components/airly/diagnostics.py new file mode 100644 index 00000000000..2471ba90eea --- /dev/null +++ b/homeassistant/components/airly/diagnostics.py @@ -0,0 +1,31 @@ +"""Diagnostics support for Airly.""" +from __future__ import annotations + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_API_KEY, + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_UNIQUE_ID, +) +from homeassistant.core import HomeAssistant + +from . import AirlyDataUpdateCoordinator +from .const import DOMAIN + +TO_REDACT = {CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_UNIQUE_ID} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict: + """Return diagnostics for a config entry.""" + coordinator: AirlyDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + diagnostics_data = { + "config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT), + "coordinator_data": coordinator.data, + } + + return diagnostics_data diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 0d5177df33e..ac956185c14 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -15,7 +15,7 @@ "longitude": "Longitud", "name": "Nom" }, - "description": "Configura la integraci\u00f3 de qualitat de l'aire Airly. Per generar la clau API, v\u00e9s a https://developer.airly.eu/register", + "description": "Per generar la clau API, v\u00e9s a https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/de.json b/homeassistant/components/airly/translations/de.json index b13798c0ae3..216a8c56c6d 100644 --- a/homeassistant/components/airly/translations/de.json +++ b/homeassistant/components/airly/translations/de.json @@ -15,7 +15,7 @@ "longitude": "L\u00e4ngengrad", "name": "Name" }, - "description": "Einrichtung der Airly-Luftqualit\u00e4t Integration. Um einen API-Schl\u00fcssel zu generieren, registriere dich auf https://developer.airly.eu/register", + "description": "Um einen API-Schl\u00fcssel zu generieren, besuche https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/en.json b/homeassistant/components/airly/translations/en.json index 0a5426c87d8..249905eff2a 100644 --- a/homeassistant/components/airly/translations/en.json +++ b/homeassistant/components/airly/translations/en.json @@ -15,7 +15,7 @@ "longitude": "Longitude", "name": "Name" }, - "description": "Set up Airly air quality integration. To generate API key go to https://developer.airly.eu/register", + "description": "To generate API key go to https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index 6730e131ac2..6a55262ac6c 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -15,7 +15,7 @@ "longitude": "Pikkuskraad", "name": "Nimi" }, - "description": "Seadista Airly \u00f5hukvaliteedi andmete sidumine. API v\u00f5tme loomiseks mine aadressile https://developer.airly.eu/register", + "description": "API-v\u00f5tme loomiseks mine aadressile https://developer.airly.eu/register", "title": "" } } diff --git a/homeassistant/components/airly/translations/fr.json b/homeassistant/components/airly/translations/fr.json index 85d1a3b478e..17a7248f7ad 100644 --- a/homeassistant/components/airly/translations/fr.json +++ b/homeassistant/components/airly/translations/fr.json @@ -15,7 +15,7 @@ "longitude": "Longitude", "name": "Nom" }, - "description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air Airly. Pour g\u00e9n\u00e9rer une cl\u00e9 API, rendez-vous sur https://developer.airly.eu/register.", + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/hu.json b/homeassistant/components/airly/translations/hu.json index f730edde85f..04d667f4079 100644 --- a/homeassistant/components/airly/translations/hu.json +++ b/homeassistant/components/airly/translations/hu.json @@ -13,9 +13,9 @@ "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, - "description": "Az Airly leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Api-kulcs l\u00e9trehoz\u00e1s\u00e1hoz nyissa meg a k\u00f6vetkez\u0151 weboldalt: https://developer.airly.eu/register", + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://developer.airly.eu/register webhelyet", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/id.json b/homeassistant/components/airly/translations/id.json index 57b4c0d95f9..bc97db4135a 100644 --- a/homeassistant/components/airly/translations/id.json +++ b/homeassistant/components/airly/translations/id.json @@ -15,7 +15,7 @@ "longitude": "Bujur", "name": "Nama" }, - "description": "Siapkan integrasi kualitas udara Airly. Untuk membuat kunci API, buka https://developer.airly.eu/register", + "description": "Untuk membuat kunci API, buka https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/it.json b/homeassistant/components/airly/translations/it.json index 170455ffb15..b96d5c8a74c 100644 --- a/homeassistant/components/airly/translations/it.json +++ b/homeassistant/components/airly/translations/it.json @@ -15,7 +15,7 @@ "longitude": "Logitudine", "name": "Nome" }, - "description": "Configurazione dell'integrazione della qualit\u00e0 dell'aria Airly. Per generare la chiave API vai su https://developer.airly.eu/register", + "description": "Per generare la chiave API, vai su https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index 14cbaf1711e..70fbca2074f 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -15,7 +15,7 @@ "longitude": "Lengtegraad", "name": "Naam" }, - "description": "Airly-integratie van luchtkwaliteit instellen. Ga naar https://developer.airly.eu/register om de API-sleutel te genereren", + "description": "Om een API sleutel te genereren ga naar https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/no.json b/homeassistant/components/airly/translations/no.json index 4c81422d93c..015e95af8f2 100644 --- a/homeassistant/components/airly/translations/no.json +++ b/homeassistant/components/airly/translations/no.json @@ -15,7 +15,7 @@ "longitude": "Lengdegrad", "name": "Navn" }, - "description": "Sett opp Airly luftkvalitet integrasjon. For \u00e5 opprette API-n\u00f8kkel, g\u00e5 til [https://developer.airly.eu/register](https://developer.airly.eu/register)", + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://developer.airly.eu/register", "title": "" } } diff --git a/homeassistant/components/airly/translations/pl.json b/homeassistant/components/airly/translations/pl.json index f205a569474..dd44f2bf635 100644 --- a/homeassistant/components/airly/translations/pl.json +++ b/homeassistant/components/airly/translations/pl.json @@ -15,7 +15,7 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "name": "Nazwa" }, - "description": "Konfiguracja integracji Airly. By wygenerowa\u0107 klucz API, przejd\u017a na stron\u0119 https://developer.airly.eu/register", + "description": "By wygenerowa\u0107 klucz API, przejd\u017a na stron\u0119 https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/pt-BR.json b/homeassistant/components/airly/translations/pt-BR.json index e1f2fe097a6..0e9913b559c 100644 --- a/homeassistant/components/airly/translations/pt-BR.json +++ b/homeassistant/components/airly/translations/pt-BR.json @@ -15,7 +15,7 @@ "longitude": "Longitude", "name": "Nome" }, - "description": "Configure a integra\u00e7\u00e3o da qualidade do ar airly. Para gerar a chave de API v\u00e1 para https://developer.airly.eu/register", + "description": "Para gerar a chave de API, acesse https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/ru.json b/homeassistant/components/airly/translations/ru.json index 41ca90a8c02..80c6a98813c 100644 --- a/homeassistant/components/airly/translations/ru.json +++ b/homeassistant/components/airly/translations/ru.json @@ -15,7 +15,7 @@ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0443 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 Airly. \u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://developer.airly.eu/register.", + "description": "\u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://developer.airly.eu/register.", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/tr.json b/homeassistant/components/airly/translations/tr.json index fcae9294da2..7f5643be2d0 100644 --- a/homeassistant/components/airly/translations/tr.json +++ b/homeassistant/components/airly/translations/tr.json @@ -15,7 +15,7 @@ "longitude": "Boylam", "name": "Ad" }, - "description": "Airly hava kalitesi entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.airly.eu/register adresine gidin.", + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://developer.airly.eu/register adresine gidin.", "title": "Airly" } } diff --git a/homeassistant/components/airly/translations/zh-Hant.json b/homeassistant/components/airly/translations/zh-Hant.json index e289bc7cd50..65f0bf8cde5 100644 --- a/homeassistant/components/airly/translations/zh-Hant.json +++ b/homeassistant/components/airly/translations/zh-Hant.json @@ -15,7 +15,7 @@ "longitude": "\u7d93\u5ea6", "name": "\u540d\u7a31" }, - "description": "\u6b32\u8a2d\u5b9a Airly \u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u8acb\u81f3 https://developer.airly.eu/register \u7522\u751f API \u91d1\u9470", + "description": "\u8acb\u81f3 https://developer.airly.eu/register \u4ee5\u7522\u751f API \u91d1\u9470", "title": "Airly" } } diff --git a/homeassistant/components/airnow/translations/ca.json b/homeassistant/components/airnow/translations/ca.json index 2db3cfad563..9a2b0aa9afc 100644 --- a/homeassistant/components/airnow/translations/ca.json +++ b/homeassistant/components/airnow/translations/ca.json @@ -17,7 +17,7 @@ "longitude": "Longitud", "radius": "Radi de l'estaci\u00f3 (milles; opcional)" }, - "description": "Configura la integraci\u00f3 de qualitat d'aire AirNow. Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/", + "description": "Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/de.json b/homeassistant/components/airnow/translations/de.json index adf9ddf85a3..3c207a39cce 100644 --- a/homeassistant/components/airnow/translations/de.json +++ b/homeassistant/components/airnow/translations/de.json @@ -17,7 +17,7 @@ "longitude": "L\u00e4ngengrad", "radius": "Stationsradius (Meilen; optional)" }, - "description": "Richte die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuche https://docs.airnowapi.org/account/request/.", + "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/en.json b/homeassistant/components/airnow/translations/en.json index 371bb270ac1..cb3081284b4 100644 --- a/homeassistant/components/airnow/translations/en.json +++ b/homeassistant/components/airnow/translations/en.json @@ -17,7 +17,7 @@ "longitude": "Longitude", "radius": "Station Radius (miles; optional)" }, - "description": "Set up AirNow air quality integration. To generate API key go to https://docs.airnowapi.org/account/request/", + "description": "To generate API key go to https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/et.json b/homeassistant/components/airnow/translations/et.json index 52b2bb618e0..3bdf8661d36 100644 --- a/homeassistant/components/airnow/translations/et.json +++ b/homeassistant/components/airnow/translations/et.json @@ -17,7 +17,7 @@ "longitude": "Pikkuskraad", "radius": "Jaama raadius (miilid; valikuline)" }, - "description": "Seadista AirNow \u00f5hukvaliteedi sidumine. API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/", + "description": "API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/", "title": "" } } diff --git a/homeassistant/components/airnow/translations/fr.json b/homeassistant/components/airnow/translations/fr.json index 1cfe1652771..2da7427e1be 100644 --- a/homeassistant/components/airnow/translations/fr.json +++ b/homeassistant/components/airnow/translations/fr.json @@ -17,7 +17,7 @@ "longitude": "Longitude", "radius": "Rayon d'action de la station (en miles, facultatif)" }, - "description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air AirNow. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://docs.airnowapi.org/account/request/", + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/hu.json b/homeassistant/components/airnow/translations/hu.json index 3f1bef471ee..52bf8cd63a0 100644 --- a/homeassistant/components/airnow/translations/hu.json +++ b/homeassistant/components/airnow/translations/hu.json @@ -17,7 +17,7 @@ "longitude": "Hossz\u00fas\u00e1g", "radius": "\u00c1llom\u00e1s sugara (m\u00e9rf\u00f6ld; opcion\u00e1lis)" }, - "description": "\u00c1ll\u00edtsa be az AirNow leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3t. Az API-kulcs el\u0151\u00e1ll\u00edt\u00e1s\u00e1hoz keresse fel a https://docs.airnowapi.org/account/request/ oldalt.", + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://docs.airnowapi.org/account/request/ webhelyet", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/id.json b/homeassistant/components/airnow/translations/id.json index 66fdff72fae..4d4ac987320 100644 --- a/homeassistant/components/airnow/translations/id.json +++ b/homeassistant/components/airnow/translations/id.json @@ -17,7 +17,7 @@ "longitude": "Bujur", "radius": "Radius Stasiun (mil; opsional)" }, - "description": "Siapkan integrasi kualitas udara AirNow. Untuk membuat kunci API buka https://docs.airnowapi.org/account/request/", + "description": "Untuk membuat kunci API buka https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/it.json b/homeassistant/components/airnow/translations/it.json index 9dda15dfbd2..77715910c75 100644 --- a/homeassistant/components/airnow/translations/it.json +++ b/homeassistant/components/airnow/translations/it.json @@ -17,7 +17,7 @@ "longitude": "Logitudine", "radius": "Raggio stazione (miglia; opzionale)" }, - "description": "Configura l'integrazione per la qualit\u00e0 dell'aria AirNow. Per generare la chiave API, vai su https://docs.airnowapi.org/account/request/", + "description": "Per generare la chiave API vai su https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/nl.json b/homeassistant/components/airnow/translations/nl.json index 090a5363823..5e5e33bf93d 100644 --- a/homeassistant/components/airnow/translations/nl.json +++ b/homeassistant/components/airnow/translations/nl.json @@ -17,7 +17,7 @@ "longitude": "Lengtegraad", "radius": "Stationsradius (mijl; optioneel)" }, - "description": "AirNow luchtkwaliteit integratie opzetten. Om een API sleutel te genereren ga naar https://docs.airnowapi.org/account/request/", + "description": "Om een API sleutel te genereren ga naar https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/no.json b/homeassistant/components/airnow/translations/no.json index 19fa7e12207..b56b7096e2b 100644 --- a/homeassistant/components/airnow/translations/no.json +++ b/homeassistant/components/airnow/translations/no.json @@ -17,7 +17,7 @@ "longitude": "Lengdegrad", "radius": "Stasjonsradius (miles; valgfritt)" }, - "description": "Konfigurer integrering av luftkvalitet i AirNow. For \u00e5 generere en API-n\u00f8kkel, g\u00e5r du til https://docs.airnowapi.org/account/request/", + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://docs.airnowapi.org/account/request/", "title": "" } } diff --git a/homeassistant/components/airnow/translations/pl.json b/homeassistant/components/airnow/translations/pl.json index fe4310607b9..f29a48dc47b 100644 --- a/homeassistant/components/airnow/translations/pl.json +++ b/homeassistant/components/airnow/translations/pl.json @@ -17,7 +17,7 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "radius": "Promie\u0144 od stacji (w milach; opcjonalnie)" }, - "description": "Konfiguracja integracji jako\u015bci powietrza AirNow. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/", + "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/pt-BR.json b/homeassistant/components/airnow/translations/pt-BR.json index fa24093b419..c1bda0098cd 100644 --- a/homeassistant/components/airnow/translations/pt-BR.json +++ b/homeassistant/components/airnow/translations/pt-BR.json @@ -17,7 +17,7 @@ "longitude": "Longitude", "radius": "Raio da Esta\u00e7\u00e3o (milhas; opcional)" }, - "description": "Configure a integra\u00e7\u00e3o da qualidade do ar AirNow. Para gerar a chave de API, acesse https://docs.airnowapi.org/account/request/", + "description": "Para gerar a chave de API, acesse https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/ru.json b/homeassistant/components/airnow/translations/ru.json index 9667accb7c4..c5a9137f4c7 100644 --- a/homeassistant/components/airnow/translations/ru.json +++ b/homeassistant/components/airnow/translations/ru.json @@ -17,7 +17,7 @@ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 (\u0432 \u043c\u0438\u043b\u044f\u0445; \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" }, - "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0443 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 AirNow. \u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://docs.airnowapi.org/account/request/.", + "description": "\u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://docs.airnowapi.org/account/request/.", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/tr.json b/homeassistant/components/airnow/translations/tr.json index 590332b496c..1f80d7805da 100644 --- a/homeassistant/components/airnow/translations/tr.json +++ b/homeassistant/components/airnow/translations/tr.json @@ -17,7 +17,7 @@ "longitude": "Boylam", "radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)" }, - "description": "AirNow hava kalitesi entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://docs.airnowapi.org/account/request/ adresine gidin.", + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://docs.airnowapi.org/account/request/ adresine gidin.", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/zh-Hant.json b/homeassistant/components/airnow/translations/zh-Hant.json index 08d7aab5878..bb18a5d8975 100644 --- a/homeassistant/components/airnow/translations/zh-Hant.json +++ b/homeassistant/components/airnow/translations/zh-Hant.json @@ -17,7 +17,7 @@ "longitude": "\u7d93\u5ea6", "radius": "\u89c0\u6e2c\u7ad9\u534a\u5f91\uff08\u82f1\u91cc\uff1b\u9078\u9805\uff09" }, - "description": "\u6b32\u8a2d\u5b9a AirNow \u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u7522\u751f API \u91d1\u9470", + "description": "\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u4ee5\u7522\u751f API \u91d1\u9470", "title": "AirNow" } } diff --git a/homeassistant/components/airtouch4/climate.py b/homeassistant/components/airtouch4/climate.py index e14130d3b31..f660c06082c 100644 --- a/homeassistant/components/airtouch4/climate.py +++ b/homeassistant/components/airtouch4/climate.py @@ -11,14 +11,8 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -29,24 +23,23 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE AT_TO_HA_STATE = { - "Heat": HVAC_MODE_HEAT, - "Cool": HVAC_MODE_COOL, - "AutoHeat": HVAC_MODE_AUTO, # airtouch reports either autoheat or autocool - "AutoCool": HVAC_MODE_AUTO, - "Auto": HVAC_MODE_AUTO, - "Dry": HVAC_MODE_DRY, - "Fan": HVAC_MODE_FAN_ONLY, + "Heat": HVACMode.HEAT, + "Cool": HVACMode.COOL, + "AutoHeat": HVACMode.AUTO, # airtouch reports either autoheat or autocool + "AutoCool": HVACMode.AUTO, + "Auto": HVACMode.AUTO, + "Dry": HVACMode.DRY, + "Fan": HVACMode.FAN_ONLY, } HA_STATE_TO_AT = { - HVAC_MODE_HEAT: "Heat", - HVAC_MODE_COOL: "Cool", - HVAC_MODE_AUTO: "Auto", - HVAC_MODE_DRY: "Dry", - HVAC_MODE_FAN_ONLY: "Fan", - HVAC_MODE_OFF: "Off", + HVACMode.HEAT: "Heat", + HVACMode.COOL: "Cool", + HVACMode.AUTO: "Auto", + HVACMode.DRY: "Dry", + HVACMode.FAN_ONLY: "Fan", + HVACMode.OFF: "Off", } AT_TO_HA_FAN_SPEED = { @@ -59,7 +52,7 @@ AT_TO_HA_FAN_SPEED = { "Turbo": "turbo", } -AT_GROUP_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] +AT_GROUP_MODES = [HVACMode.OFF, HVACMode.FAN_ONLY] HA_FAN_SPEED_TO_AT = {value: key for key, value in AT_TO_HA_FAN_SPEED.items()} @@ -90,7 +83,9 @@ async def async_setup_entry( class AirtouchAC(CoordinatorEntity, ClimateEntity): """Representation of an AirTouch 4 ac.""" - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) _attr_temperature_unit = TEMP_CELSIUS def __init__(self, coordinator, ac_number, info): @@ -147,7 +142,7 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity): """Return hvac target hvac state.""" is_off = self._unit.PowerState == "Off" if is_off: - return HVAC_MODE_OFF + return HVACMode.OFF return AT_TO_HA_STATE[self._airtouch.acs[self._ac_number].AcMode] @@ -156,7 +151,7 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity): """Return the list of available operation modes.""" airtouch_modes = self._airtouch.GetSupportedCoolingModesForAc(self._ac_number) modes = [AT_TO_HA_STATE[mode] for mode in airtouch_modes] - modes.append(HVAC_MODE_OFF) + modes.append(HVACMode.OFF) return modes async def async_set_hvac_mode(self, hvac_mode): @@ -164,7 +159,7 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity): if hvac_mode not in HA_STATE_TO_AT: raise ValueError(f"Unsupported HVAC mode: {hvac_mode}") - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: return await self.async_turn_off() await self._airtouch.SetCoolingModeForAc( self._ac_number, HA_STATE_TO_AT[hvac_mode] @@ -204,7 +199,7 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity): class AirtouchGroup(CoordinatorEntity, ClimateEntity): """Representation of an AirTouch 4 group.""" - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_temperature_unit = TEMP_CELSIUS _attr_hvac_modes = AT_GROUP_MODES @@ -267,18 +262,18 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity): # there are other power states that aren't 'on' but still count as on (eg. 'Turbo') is_off = self._unit.PowerState == "Off" if is_off: - return HVAC_MODE_OFF + return HVACMode.OFF - return HVAC_MODE_FAN_ONLY + return HVACMode.FAN_ONLY async def async_set_hvac_mode(self, hvac_mode): """Set new operation mode.""" if hvac_mode not in HA_STATE_TO_AT: raise ValueError(f"Unsupported HVAC mode: {hvac_mode}") - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: return await self.async_turn_off() - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: await self.async_turn_on() self._unit = self._airtouch.GetGroups()[self._group_number] _LOGGER.debug( diff --git a/homeassistant/components/airvisual/translations/sensor.fr.json b/homeassistant/components/airvisual/translations/sensor.fr.json index 47feff6bd79..2f9aacc775b 100644 --- a/homeassistant/components/airvisual/translations/sensor.fr.json +++ b/homeassistant/components/airvisual/translations/sensor.fr.json @@ -10,7 +10,7 @@ }, "airvisual__pollutant_level": { "good": "Bon", - "hazardous": "Hasardeux", + "hazardous": "Dangereux", "moderate": "Mod\u00e9r\u00e9", "unhealthy": "Malsain", "unhealthy_sensitive": "Malsain pour les groupes sensibles", diff --git a/homeassistant/components/airzone/__init__.py b/homeassistant/components/airzone/__init__.py index 6952ac2a42d..39f1fc978c3 100644 --- a/homeassistant/components/airzone/__init__.py +++ b/homeassistant/components/airzone/__init__.py @@ -1,23 +1,30 @@ """The Airzone integration.""" from __future__ import annotations +import logging from typing import Any -from aioairzone.common import ConnectionOptions from aioairzone.const import ( AZD_ID, + AZD_MAC, AZD_NAME, AZD_SYSTEM, AZD_THERMOSTAT_FW, AZD_THERMOSTAT_MODEL, + AZD_WEBSERVER, AZD_ZONES, + DEFAULT_SYSTEM_ID, ) -from aioairzone.localapi import AirzoneLocalApi +from aioairzone.localapi import AirzoneLocalApi, ConnectionOptions from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import aiohttp_client +from homeassistant.const import CONF_HOST, CONF_ID, CONF_PORT, Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import ( + aiohttp_client, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -26,10 +33,20 @@ from .coordinator import AirzoneUpdateCoordinator PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR] +_LOGGER = logging.getLogger(__name__) + class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator]): """Define an Airzone entity.""" + def get_airzone_value(self, key) -> Any: + """Return Airzone entity value by key.""" + raise NotImplementedError() + + +class AirzoneZoneEntity(AirzoneEntity): + """Define an Airzone Zone entity.""" + def __init__( self, coordinator: AirzoneUpdateCoordinator, @@ -47,12 +64,15 @@ class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator]): self._attr_device_info: DeviceInfo = { "identifiers": {(DOMAIN, f"{entry.entry_id}_{system_zone_id}")}, "manufacturer": MANUFACTURER, - "model": self.get_zone_value(AZD_THERMOSTAT_MODEL), + "model": self.get_airzone_value(AZD_THERMOSTAT_MODEL), "name": f"Airzone [{system_zone_id}] {zone_data[AZD_NAME]}", - "sw_version": self.get_zone_value(AZD_THERMOSTAT_FW), + "sw_version": self.get_airzone_value(AZD_THERMOSTAT_FW), } + self._attr_unique_id = ( + entry.entry_id if entry.unique_id is None else entry.unique_id + ) - def get_zone_value(self, key): + def get_airzone_value(self, key) -> Any: """Return zone value by key.""" value = None if self.system_zone_id in self.coordinator.data[AZD_ZONES]: @@ -62,17 +82,58 @@ class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator]): return value +async def _async_migrate_unique_ids( + hass: HomeAssistant, + entry: ConfigEntry, + coordinator: AirzoneUpdateCoordinator, +) -> None: + """Migrate entities when the mac address gets discovered.""" + + @callback + def _async_migrator(entity_entry: er.RegistryEntry) -> dict[str, Any] | None: + updates = None + + unique_id = entry.unique_id + entry_id = entry.entry_id + entity_unique_id = entity_entry.unique_id + + if entity_unique_id.startswith(entry_id): + new_unique_id = f"{unique_id}{entity_unique_id[len(entry_id):]}" + _LOGGER.info( + "Migrating unique_id from [%s] to [%s]", + entity_unique_id, + new_unique_id, + ) + updates = {"new_unique_id": new_unique_id} + + return updates + + if ( + entry.unique_id is None + and AZD_WEBSERVER in coordinator.data + and AZD_MAC in coordinator.data[AZD_WEBSERVER] + and (mac := coordinator.data[AZD_WEBSERVER][AZD_MAC]) is not None + ): + updates: dict[str, Any] = { + "unique_id": dr.format_mac(mac), + } + hass.config_entries.async_update_entry(entry, **updates) + + await er.async_migrate_entries(hass, entry.entry_id, _async_migrator) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Airzone from a config entry.""" options = ConnectionOptions( entry.data[CONF_HOST], entry.data[CONF_PORT], + entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID), ) airzone = AirzoneLocalApi(aiohttp_client.async_get_clientsession(hass), options) - coordinator = AirzoneUpdateCoordinator(hass, airzone) await coordinator.async_config_entry_first_refresh() + await _async_migrate_unique_ids(hass, entry, coordinator) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator diff --git a/homeassistant/components/airzone/binary_sensor.py b/homeassistant/components/airzone/binary_sensor.py index 1d0c76906e8..dd0e4a5b768 100644 --- a/homeassistant/components/airzone/binary_sensor.py +++ b/homeassistant/components/airzone/binary_sensor.py @@ -7,6 +7,7 @@ from typing import Any, Final from aioairzone.const import ( AZD_AIR_DEMAND, + AZD_BATTERY_LOW, AZD_ERRORS, AZD_FLOOR_DEMAND, AZD_NAME, @@ -15,8 +16,7 @@ from aioairzone.const import ( ) from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_PROBLEM, - DEVICE_CLASS_RUNNING, + BinarySensorDeviceClass, BinarySensorEntity, BinarySensorEntityDescription, ) @@ -25,7 +25,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import AirzoneEntity +from . import AirzoneEntity, AirzoneZoneEntity from .const import DOMAIN from .coordinator import AirzoneUpdateCoordinator @@ -37,14 +37,19 @@ class AirzoneBinarySensorEntityDescription(BinarySensorEntityDescription): attributes: dict[str, str] | None = None -BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = ( +ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = ( AirzoneBinarySensorEntityDescription( - device_class=DEVICE_CLASS_RUNNING, + device_class=BinarySensorDeviceClass.RUNNING, key=AZD_AIR_DEMAND, name="Air Demand", ), AirzoneBinarySensorEntityDescription( - device_class=DEVICE_CLASS_RUNNING, + device_class=BinarySensorDeviceClass.BATTERY, + key=AZD_BATTERY_LOW, + name="Battery Low", + ), + AirzoneBinarySensorEntityDescription( + device_class=BinarySensorDeviceClass.RUNNING, key=AZD_FLOOR_DEMAND, name="Floor Demand", ), @@ -52,7 +57,7 @@ BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]] = ( attributes={ "errors": AZD_ERRORS, }, - device_class=DEVICE_CLASS_PROBLEM, + device_class=BinarySensorDeviceClass.PROBLEM, entity_category=EntityCategory.DIAGNOSTIC, key=AZD_PROBLEMS, name="Problem", @@ -66,12 +71,12 @@ async def async_setup_entry( """Add Airzone binary sensors from a config_entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - binary_sensors = [] + binary_sensors: list[AirzoneBinarySensor] = [] for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): - for description in BINARY_SENSOR_TYPES: + for description in ZONE_BINARY_SENSOR_TYPES: if description.key in zone_data: binary_sensors.append( - AirzoneBinarySensor( + AirzoneZoneBinarySensor( coordinator, description, entry, @@ -84,7 +89,28 @@ async def async_setup_entry( class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity): - """Define an Airzone sensor.""" + """Define an Airzone binary sensor.""" + + entity_description: AirzoneBinarySensorEntityDescription + + @property + def extra_state_attributes(self) -> Mapping[str, Any] | None: + """Return state attributes.""" + if not self.entity_description.attributes: + return None + return { + key: self.get_airzone_value(val) + for key, val in self.entity_description.attributes.items() + } + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self.get_airzone_value(self.entity_description.key) + + +class AirzoneZoneBinarySensor(AirzoneZoneEntity, AirzoneBinarySensor): + """Define an Airzone Zone binary sensor.""" def __init__( self, @@ -96,19 +122,9 @@ class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity): ) -> None: """Initialize.""" super().__init__(coordinator, entry, system_zone_id, zone_data) + self._attr_name = f"{zone_data[AZD_NAME]} {description.name}" - self._attr_unique_id = f"{entry.entry_id}_{system_zone_id}_{description.key}" - self.attributes = description.attributes + self._attr_unique_id = ( + f"{self._attr_unique_id}_{system_zone_id}_{description.key}" + ) self.entity_description = description - - @property - def extra_state_attributes(self) -> Mapping[str, Any] | None: - """Return state attributes.""" - if not self.attributes: - return None - return {key: self.get_zone_value(val) for key, val in self.attributes.items()} - - @property - def is_on(self) -> bool | None: - """Return true if the binary sensor is on.""" - return self.get_zone_value(self.entity_description.key) diff --git a/homeassistant/components/airzone/climate.py b/homeassistant/components/airzone/climate.py index f9a32e8e5ff..1ec7dfabcfa 100644 --- a/homeassistant/components/airzone/climate.py +++ b/homeassistant/components/airzone/climate.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Final +from typing import Any, Final from aioairzone.common import OperationMode from aioairzone.const import ( @@ -26,23 +26,12 @@ from aioairzone.const import ( AZD_ZONES, ) from aioairzone.exceptions import AirzoneError -from aiohttp.client_exceptions import ClientConnectorError from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE @@ -50,35 +39,35 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import AirzoneEntity +from . import AirzoneZoneEntity from .const import API_TEMPERATURE_STEP, DOMAIN, TEMP_UNIT_LIB_TO_HASS from .coordinator import AirzoneUpdateCoordinator _LOGGER = logging.getLogger(__name__) -HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationMode, str]] = { - OperationMode.STOP: CURRENT_HVAC_OFF, - OperationMode.COOLING: CURRENT_HVAC_COOL, - OperationMode.HEATING: CURRENT_HVAC_HEAT, - OperationMode.FAN: CURRENT_HVAC_FAN, - OperationMode.DRY: CURRENT_HVAC_DRY, +HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationMode, HVACAction]] = { + OperationMode.STOP: HVACAction.OFF, + OperationMode.COOLING: HVACAction.COOLING, + OperationMode.HEATING: HVACAction.HEATING, + OperationMode.FAN: HVACAction.FAN, + OperationMode.DRY: HVACAction.DRYING, } -HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, str]] = { - OperationMode.STOP: HVAC_MODE_OFF, - OperationMode.COOLING: HVAC_MODE_COOL, - OperationMode.HEATING: HVAC_MODE_HEAT, - OperationMode.FAN: HVAC_MODE_FAN_ONLY, - OperationMode.DRY: HVAC_MODE_DRY, - OperationMode.AUTO: HVAC_MODE_HEAT_COOL, +HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, HVACMode]] = { + OperationMode.STOP: HVACMode.OFF, + OperationMode.COOLING: HVACMode.COOL, + OperationMode.HEATING: HVACMode.HEAT, + OperationMode.FAN: HVACMode.FAN_ONLY, + OperationMode.DRY: HVACMode.DRY, + OperationMode.AUTO: HVACMode.HEAT_COOL, } -HVAC_MODE_HASS_TO_LIB: Final[dict[str, OperationMode]] = { - HVAC_MODE_OFF: OperationMode.STOP, - HVAC_MODE_COOL: OperationMode.COOLING, - HVAC_MODE_HEAT: OperationMode.HEATING, - HVAC_MODE_FAN_ONLY: OperationMode.FAN, - HVAC_MODE_DRY: OperationMode.DRY, - HVAC_MODE_HEAT_COOL: OperationMode.AUTO, +HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = { + HVACMode.OFF: OperationMode.STOP, + HVACMode.COOL: OperationMode.COOLING, + HVACMode.HEAT: OperationMode.HEATING, + HVACMode.FAN_ONLY: OperationMode.FAN, + HVACMode.DRY: OperationMode.DRY, + HVACMode.HEAT_COOL: OperationMode.AUTO, } @@ -98,7 +87,7 @@ async def async_setup_entry( ) -class AirzoneClimate(AirzoneEntity, ClimateEntity): +class AirzoneClimate(AirzoneZoneEntity, ClimateEntity): """Define an Airzone sensor.""" def __init__( @@ -110,61 +99,74 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity): ) -> None: """Initialize Airzone climate entity.""" super().__init__(coordinator, entry, system_zone_id, zone_data) + self._attr_name = f"{zone_data[AZD_NAME]}" - self._attr_unique_id = f"{entry.entry_id}_{system_zone_id}" - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_unique_id = f"{self._attr_unique_id}_{system_zone_id}" + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE self._attr_target_temperature_step = API_TEMPERATURE_STEP - self._attr_max_temp = self.get_zone_value(AZD_TEMP_MAX) - self._attr_min_temp = self.get_zone_value(AZD_TEMP_MIN) + self._attr_max_temp = self.get_airzone_value(AZD_TEMP_MAX) + self._attr_min_temp = self.get_airzone_value(AZD_TEMP_MIN) self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[ - self.get_zone_value(AZD_TEMP_UNIT) + self.get_airzone_value(AZD_TEMP_UNIT) ] self._attr_hvac_modes = [ - HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_zone_value(AZD_MODES) + HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES) ] self._async_update_attrs() - async def _async_update_hvac_params(self, params) -> None: + async def _async_update_hvac_params(self, params: dict[str, Any]) -> None: """Send HVAC parameters to API.""" + _params = { + API_SYSTEM_ID: self.system_id, + API_ZONE_ID: self.zone_id, + **params, + } + _LOGGER.debug("update_hvac_params=%s", _params) try: - await self.coordinator.airzone.put_hvac(params) - except (AirzoneError, ClientConnectorError) as error: + await self.coordinator.airzone.put_hvac(_params) + except AirzoneError as error: raise HomeAssistantError( f"Failed to set zone {self.name}: {error}" ) from error else: self.coordinator.async_set_updated_data(self.coordinator.airzone.data()) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: - """Set hvac mode.""" + async def async_turn_on(self) -> None: + """Turn the entity on.""" params = { - API_SYSTEM_ID: self.system_id, - API_ZONE_ID: self.zone_id, + API_ON: 1, } - if hvac_mode == HVAC_MODE_OFF: + await self._async_update_hvac_params(params) + + async def async_turn_off(self) -> None: + """Turn the entity off.""" + params = { + API_ON: 0, + } + await self._async_update_hvac_params(params) + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set hvac mode.""" + params = {} + if hvac_mode == HVACMode.OFF: params[API_ON] = 0 else: mode = HVAC_MODE_HASS_TO_LIB[hvac_mode] - if mode != self.get_zone_value(AZD_MODE): - if self.get_zone_value(AZD_MASTER): + if mode != self.get_airzone_value(AZD_MODE): + if self.get_airzone_value(AZD_MASTER): params[API_MODE] = mode else: raise HomeAssistantError( f"Mode can't be changed on slave zone {self.name}" ) params[API_ON] = 1 - _LOGGER.debug("Set hvac_mode=%s params=%s", hvac_mode, params) await self._async_update_hvac_params(params) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - temp = kwargs.get(ATTR_TEMPERATURE) params = { - API_SYSTEM_ID: self.system_id, - API_ZONE_ID: self.zone_id, - API_SET_POINT: temp, + API_SET_POINT: kwargs.get(ATTR_TEMPERATURE), } - _LOGGER.debug("Set temp=%s params=%s", temp, params) await self._async_update_hvac_params(params) @callback @@ -176,16 +178,16 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity): @callback def _async_update_attrs(self) -> None: """Update climate attributes.""" - self._attr_current_temperature = self.get_zone_value(AZD_TEMP) - self._attr_current_humidity = self.get_zone_value(AZD_HUMIDITY) - if self.get_zone_value(AZD_ON): - mode = self.get_zone_value(AZD_MODE) + self._attr_current_temperature = self.get_airzone_value(AZD_TEMP) + self._attr_current_humidity = self.get_airzone_value(AZD_HUMIDITY) + if self.get_airzone_value(AZD_ON): + mode = self.get_airzone_value(AZD_MODE) self._attr_hvac_mode = HVAC_MODE_LIB_TO_HASS[mode] - if self.get_zone_value(AZD_DEMAND): + if self.get_airzone_value(AZD_DEMAND): self._attr_hvac_action = HVAC_ACTION_LIB_TO_HASS[mode] else: - self._attr_hvac_action = CURRENT_HVAC_IDLE + self._attr_hvac_action = HVACAction.IDLE else: - self._attr_hvac_action = CURRENT_HVAC_OFF - self._attr_hvac_mode = HVAC_MODE_OFF - self._attr_target_temperature = self.get_zone_value(AZD_TEMP_SET) + self._attr_hvac_action = HVACAction.OFF + self._attr_hvac_mode = HVACMode.OFF + self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET) diff --git a/homeassistant/components/airzone/config_flow.py b/homeassistant/components/airzone/config_flow.py index 57dde088820..89a2d7f1f9e 100644 --- a/homeassistant/components/airzone/config_flow.py +++ b/homeassistant/components/airzone/config_flow.py @@ -3,18 +3,30 @@ from __future__ import annotations from typing import Any -from aioairzone.common import ConnectionOptions -from aioairzone.exceptions import InvalidHost -from aioairzone.localapi import AirzoneLocalApi -from aiohttp.client_exceptions import ClientConnectorError +from aioairzone.const import DEFAULT_PORT, DEFAULT_SYSTEM_ID +from aioairzone.exceptions import AirzoneError, InvalidSystem +from aioairzone.localapi import AirzoneLocalApi, ConnectionOptions import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_ID, CONF_PORT from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.device_registry import format_mac -from .const import DEFAULT_LOCAL_API_PORT, DOMAIN +from .const import DOMAIN + +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } +) +SYSTEM_ID_SCHEMA = CONFIG_SCHEMA.extend( + { + vol.Required(CONF_ID, default=1): int, + } +) class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -24,39 +36,43 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step.""" + data_schema = CONFIG_SCHEMA errors = {} if user_input is not None: - self._async_abort_entries_match( - { - CONF_HOST: user_input[CONF_HOST], - CONF_PORT: user_input[CONF_PORT], - } - ) + self._async_abort_entries_match(user_input) airzone = AirzoneLocalApi( aiohttp_client.async_get_clientsession(self.hass), ConnectionOptions( user_input[CONF_HOST], user_input[CONF_PORT], + user_input.get(CONF_ID, DEFAULT_SYSTEM_ID), ), ) try: - await airzone.validate_airzone() - except (ClientConnectorError, InvalidHost): + mac = await airzone.validate() + except InvalidSystem: + data_schema = SYSTEM_ID_SCHEMA + errors[CONF_ID] = "invalid_system_id" + except AirzoneError: errors["base"] = "cannot_connect" else: + if mac: + await self.async_set_unique_id(format_mac(mac)) + self._abort_if_unique_id_configured( + updates={ + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], + } + ) + title = f"Airzone {user_input[CONF_HOST]}:{user_input[CONF_PORT]}" return self.async_create_entry(title=title, data=user_input) return self.async_show_form( step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Required(CONF_PORT, default=DEFAULT_LOCAL_API_PORT): int, - } - ), + data_schema=data_schema, errors=errors, ) diff --git a/homeassistant/components/airzone/const.py b/homeassistant/components/airzone/const.py index 094e2319476..345a07692c5 100644 --- a/homeassistant/components/airzone/const.py +++ b/homeassistant/components/airzone/const.py @@ -11,7 +11,6 @@ MANUFACTURER: Final = "Airzone" AIOAIRZONE_DEVICE_TIMEOUT_SEC: Final = 10 API_TEMPERATURE_STEP: Final = 0.5 -DEFAULT_LOCAL_API_PORT: Final = 3000 TEMP_UNIT_LIB_TO_HASS: Final[dict[TemperatureUnit, str]] = { TemperatureUnit.CELSIUS: TEMP_CELSIUS, diff --git a/homeassistant/components/airzone/coordinator.py b/homeassistant/components/airzone/coordinator.py index f81b75b42bf..9e1dc44bb6c 100644 --- a/homeassistant/components/airzone/coordinator.py +++ b/homeassistant/components/airzone/coordinator.py @@ -4,8 +4,8 @@ from __future__ import annotations from datetime import timedelta import logging +from aioairzone.exceptions import AirzoneError from aioairzone.localapi import AirzoneLocalApi -from aiohttp.client_exceptions import ClientConnectorError import async_timeout from homeassistant.core import HomeAssistant @@ -36,7 +36,7 @@ class AirzoneUpdateCoordinator(DataUpdateCoordinator): """Update data via library.""" async with async_timeout.timeout(AIOAIRZONE_DEVICE_TIMEOUT_SEC): try: - await self.airzone.update_airzone() - except ClientConnectorError as error: + await self.airzone.update() + except AirzoneError as error: raise UpdateFailed(error) from error return self.airzone.data() diff --git a/homeassistant/components/airzone/diagnostics.py b/homeassistant/components/airzone/diagnostics.py new file mode 100644 index 00000000000..8baa106beb1 --- /dev/null +++ b/homeassistant/components/airzone/diagnostics.py @@ -0,0 +1,29 @@ +"""Support for the Airzone diagnostics.""" +from __future__ import annotations + +from typing import Any + +from aioairzone.const import AZD_MAC + +from homeassistant.components.diagnostics.util import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import AirzoneUpdateCoordinator + +TO_REDACT = [ + AZD_MAC, +] + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: AirzoneUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + return { + "info": async_redact_data(config_entry.data, TO_REDACT), + "data": async_redact_data(coordinator.data, TO_REDACT), + } diff --git a/homeassistant/components/airzone/manifest.json b/homeassistant/components/airzone/manifest.json index 821f564f176..a6ea814fe9c 100644 --- a/homeassistant/components/airzone/manifest.json +++ b/homeassistant/components/airzone/manifest.json @@ -3,7 +3,7 @@ "name": "Airzone", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airzone", - "requirements": ["aioairzone==0.3.3"], + "requirements": ["aioairzone==0.4.2"], "codeowners": ["@Noltari"], "iot_class": "local_polling", "loggers": ["aioairzone"] diff --git a/homeassistant/components/airzone/sensor.py b/homeassistant/components/airzone/sensor.py index 931e74a495d..f41add5053a 100644 --- a/homeassistant/components/airzone/sensor.py +++ b/homeassistant/components/airzone/sensor.py @@ -6,38 +6,34 @@ from typing import Any, Final from aioairzone.const import AZD_HUMIDITY, AZD_NAME, AZD_TEMP, AZD_TEMP_UNIT, AZD_ZONES from homeassistant.components.sensor import ( - STATE_CLASS_MEASUREMENT, + SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - PERCENTAGE, - TEMP_CELSIUS, -) +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import AirzoneEntity +from . import AirzoneEntity, AirzoneZoneEntity from .const import DOMAIN, TEMP_UNIT_LIB_TO_HASS from .coordinator import AirzoneUpdateCoordinator -SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = ( +ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = ( SensorEntityDescription( - device_class=DEVICE_CLASS_TEMPERATURE, + device_class=SensorDeviceClass.TEMPERATURE, key=AZD_TEMP, name="Temperature", native_unit_of_measurement=TEMP_CELSIUS, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( - device_class=DEVICE_CLASS_HUMIDITY, + device_class=SensorDeviceClass.HUMIDITY, key=AZD_HUMIDITY, name="Humidity", native_unit_of_measurement=PERCENTAGE, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), ) @@ -48,12 +44,12 @@ async def async_setup_entry( """Add Airzone sensors from a config_entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - sensors = [] + sensors: list[AirzoneSensor] = [] for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): - for description in SENSOR_TYPES: + for description in ZONE_SENSOR_TYPES: if description.key in zone_data: sensors.append( - AirzoneSensor( + AirzoneZoneSensor( coordinator, description, entry, @@ -68,6 +64,15 @@ async def async_setup_entry( class AirzoneSensor(AirzoneEntity, SensorEntity): """Define an Airzone sensor.""" + @property + def native_value(self): + """Return the state.""" + return self.get_airzone_value(self.entity_description.key) + + +class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor): + """Define an Airzone Zone sensor.""" + def __init__( self, coordinator: AirzoneUpdateCoordinator, @@ -78,16 +83,14 @@ class AirzoneSensor(AirzoneEntity, SensorEntity): ) -> None: """Initialize.""" super().__init__(coordinator, entry, system_zone_id, zone_data) + self._attr_name = f"{zone_data[AZD_NAME]} {description.name}" - self._attr_unique_id = f"{entry.entry_id}_{system_zone_id}_{description.key}" + self._attr_unique_id = ( + f"{self._attr_unique_id}_{system_zone_id}_{description.key}" + ) self.entity_description = description if description.key == AZD_TEMP: self._attr_native_unit_of_measurement = TEMP_UNIT_LIB_TO_HASS.get( - self.get_zone_value(AZD_TEMP_UNIT) + self.get_airzone_value(AZD_TEMP_UNIT) ) - - @property - def native_value(self): - """Return the state.""" - return self.get_zone_value(self.entity_description.key) diff --git a/homeassistant/components/airzone/strings.json b/homeassistant/components/airzone/strings.json index 7de25789922..855b5615482 100644 --- a/homeassistant/components/airzone/strings.json +++ b/homeassistant/components/airzone/strings.json @@ -4,7 +4,8 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_system_id": "Invalid Airzone System ID" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/ca.json b/homeassistant/components/airzone/translations/ca.json index bbff47a9904..111a53fcf3a 100644 --- a/homeassistant/components/airzone/translations/ca.json +++ b/homeassistant/components/airzone/translations/ca.json @@ -4,7 +4,8 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "Ha fallat la connexi\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_system_id": "ID de sistema Airzone inv\u00e0lid" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/cs.json b/homeassistant/components/airzone/translations/cs.json new file mode 100644 index 00000000000..aad89c1cbe3 --- /dev/null +++ b/homeassistant/components/airzone/translations/cs.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airzone/translations/de.json b/homeassistant/components/airzone/translations/de.json index 7b3f5030f06..2edb50330f4 100644 --- a/homeassistant/components/airzone/translations/de.json +++ b/homeassistant/components/airzone/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_system_id": "Ung\u00fcltige Airzone-System-ID" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/el.json b/homeassistant/components/airzone/translations/el.json index 7b04fe27743..13da6efe27d 100644 --- a/homeassistant/components/airzone/translations/el.json +++ b/homeassistant/components/airzone/translations/el.json @@ -4,7 +4,8 @@ "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_system_id": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 Airzone" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/en.json b/homeassistant/components/airzone/translations/en.json index b24e62fa34e..85da81afd55 100644 --- a/homeassistant/components/airzone/translations/en.json +++ b/homeassistant/components/airzone/translations/en.json @@ -4,7 +4,8 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect" + "cannot_connect": "Failed to connect", + "invalid_system_id": "Invalid Airzone System ID" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/et.json b/homeassistant/components/airzone/translations/et.json index 307aa0de0a5..dff9d1173f6 100644 --- a/homeassistant/components/airzone/translations/et.json +++ b/homeassistant/components/airzone/translations/et.json @@ -4,7 +4,8 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { - "cannot_connect": "\u00dchendamine nurjus" + "cannot_connect": "\u00dchendamine nurjus", + "invalid_system_id": "Airzone'i s\u00fcsteemi ID on vigane" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/fr.json b/homeassistant/components/airzone/translations/fr.json index 1fdf1e4397b..40d22ad8bd9 100644 --- a/homeassistant/components/airzone/translations/fr.json +++ b/homeassistant/components/airzone/translations/fr.json @@ -4,7 +4,8 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { - "cannot_connect": "\u00c9chec de connexion" + "cannot_connect": "\u00c9chec de connexion", + "invalid_system_id": "ID syst\u00e8me Airzone non valide" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/hu.json b/homeassistant/components/airzone/translations/hu.json index 88e7449a1c4..9a835f88dfc 100644 --- a/homeassistant/components/airzone/translations/hu.json +++ b/homeassistant/components/airzone/translations/hu.json @@ -4,7 +4,8 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_system_id": "\u00c9rv\u00e9nytelen Airzone rendszerazonos\u00edt\u00f3" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/id.json b/homeassistant/components/airzone/translations/id.json index c37058bac34..ec37639811b 100644 --- a/homeassistant/components/airzone/translations/id.json +++ b/homeassistant/components/airzone/translations/id.json @@ -4,7 +4,8 @@ "already_configured": "Perangkat sudah dikonfigurasi" }, "error": { - "cannot_connect": "Gagal terhubung" + "cannot_connect": "Gagal terhubung", + "invalid_system_id": "ID Sistem Airzone Tidak Valid" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/it.json b/homeassistant/components/airzone/translations/it.json index db377b36fcc..32f8c67b545 100644 --- a/homeassistant/components/airzone/translations/it.json +++ b/homeassistant/components/airzone/translations/it.json @@ -4,7 +4,8 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi" + "cannot_connect": "Impossibile connettersi", + "invalid_system_id": "ID sistema Airzone non valido" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/ja.json b/homeassistant/components/airzone/translations/ja.json index 71b8bf1c908..28ebf809dbf 100644 --- a/homeassistant/components/airzone/translations/ja.json +++ b/homeassistant/components/airzone/translations/ja.json @@ -4,7 +4,8 @@ "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" }, "error": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_system_id": "Airzone\u30b7\u30b9\u30c6\u30e0ID\u304c\u7121\u52b9\u3067\u3059" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/nl.json b/homeassistant/components/airzone/translations/nl.json index 4611c23eb1e..0e84f756de1 100644 --- a/homeassistant/components/airzone/translations/nl.json +++ b/homeassistant/components/airzone/translations/nl.json @@ -4,7 +4,8 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kon niet verbinden", + "invalid_system_id": "Ongeldige Airzone systeem ID" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/no.json b/homeassistant/components/airzone/translations/no.json index f078016b761..6eeaee3a53a 100644 --- a/homeassistant/components/airzone/translations/no.json +++ b/homeassistant/components/airzone/translations/no.json @@ -4,7 +4,8 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Tilkobling mislyktes" + "cannot_connect": "Tilkobling mislyktes", + "invalid_system_id": "Ugyldig Airzone System ID" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/pl.json b/homeassistant/components/airzone/translations/pl.json index 38dc359b248..e389618ff80 100644 --- a/homeassistant/components/airzone/translations/pl.json +++ b/homeassistant/components/airzone/translations/pl.json @@ -4,7 +4,8 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_system_id": "Nieprawid\u0142owy identyfikator systemu Airzone" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/pt-BR.json b/homeassistant/components/airzone/translations/pt-BR.json index 1a8df1fef99..c2668c937b4 100644 --- a/homeassistant/components/airzone/translations/pt-BR.json +++ b/homeassistant/components/airzone/translations/pt-BR.json @@ -4,7 +4,8 @@ "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "invalid_system_id": "ID do sistema Airzone inv\u00e1lido" }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/ru.json b/homeassistant/components/airzone/translations/ru.json index 6032b0bdf00..d480866b262 100644 --- a/homeassistant/components/airzone/translations/ru.json +++ b/homeassistant/components/airzone/translations/ru.json @@ -4,7 +4,8 @@ "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." }, "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_system_id": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0438\u0441\u0442\u0435\u043c\u044b Airzone." }, "step": { "user": { diff --git a/homeassistant/components/airzone/translations/tr.json b/homeassistant/components/airzone/translations/tr.json new file mode 100644 index 00000000000..c911478ec32 --- /dev/null +++ b/homeassistant/components/airzone/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_system_id": "Ge\u00e7ersiz Airzone Sistem Kimli\u011fi" + }, + "step": { + "user": { + "data": { + "host": "Sunucu", + "port": "Port" + }, + "description": "Airzone entegrasyonunu ayarlay\u0131n." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airzone/translations/zh-Hant.json b/homeassistant/components/airzone/translations/zh-Hant.json index 01e2db9730b..42166fe39ec 100644 --- a/homeassistant/components/airzone/translations/zh-Hant.json +++ b/homeassistant/components/airzone/translations/zh-Hant.json @@ -4,7 +4,8 @@ "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_system_id": "\u7121\u6548\u7684 Airzone \u7cfb\u7d71 ID" }, "step": { "user": { diff --git a/homeassistant/components/aladdin_connect/const.py b/homeassistant/components/aladdin_connect/const.py index 7bfea738cef..3069680c753 100644 --- a/homeassistant/components/aladdin_connect/const.py +++ b/homeassistant/components/aladdin_connect/const.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Final -from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN +from homeassistant.components.cover import CoverEntityFeature from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING NOTIFICATION_ID: Final = "aladdin_notification" @@ -16,4 +16,4 @@ STATES_MAP: Final[dict[str, str]] = { "closing": STATE_CLOSING, } -SUPPORTED_FEATURES: Final = SUPPORT_OPEN | SUPPORT_CLOSE +SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py index 7865a71aef9..05c24fc9a37 100644 --- a/homeassistant/components/aladdin_connect/cover.py +++ b/homeassistant/components/aladdin_connect/cover.py @@ -58,7 +58,7 @@ def setup_platform( _LOGGER.error("%s", ex) persistent_notification.create( hass, - "Error: {ex}
You will need to restart hass after fixing.", + f"Error: {ex}
You will need to restart hass after fixing.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 082327fdec8..d89dbd280ef 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -27,24 +27,25 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType -from .const import ( +from .const import ( # noqa: F401 + ATTR_CHANGED_BY, + ATTR_CODE_ARM_REQUIRED, + DOMAIN, + FORMAT_NUMBER, + FORMAT_TEXT, SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_CUSTOM_BYPASS, SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_NIGHT, SUPPORT_ALARM_ARM_VACATION, SUPPORT_ALARM_TRIGGER, + AlarmControlPanelEntityFeature, + CodeFormat, ) _LOGGER: Final = logging.getLogger(__name__) -DOMAIN: Final = "alarm_control_panel" SCAN_INTERVAL: Final = timedelta(seconds=30) -ATTR_CHANGED_BY: Final = "changed_by" -FORMAT_TEXT: Final = "text" -FORMAT_NUMBER: Final = "number" -ATTR_CODE_ARM_REQUIRED: Final = "code_arm_required" - ENTITY_ID_FORMAT: Final = DOMAIN + ".{}" ALARM_SERVICE_SCHEMA: Final = make_entity_service_schema( @@ -58,7 +59,7 @@ PLATFORM_SCHEMA_BASE: Final = cv.PLATFORM_SCHEMA_BASE async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Track states and offer events for sensors.""" component = hass.data[DOMAIN] = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL + _LOGGER, DOMAIN, hass, SCAN_INTERVAL ) await component.async_setup(config) @@ -70,37 +71,37 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: SERVICE_ALARM_ARM_HOME, ALARM_SERVICE_SCHEMA, "async_alarm_arm_home", - [SUPPORT_ALARM_ARM_HOME], + [AlarmControlPanelEntityFeature.ARM_HOME], ) component.async_register_entity_service( SERVICE_ALARM_ARM_AWAY, ALARM_SERVICE_SCHEMA, "async_alarm_arm_away", - [SUPPORT_ALARM_ARM_AWAY], + [AlarmControlPanelEntityFeature.ARM_AWAY], ) component.async_register_entity_service( SERVICE_ALARM_ARM_NIGHT, ALARM_SERVICE_SCHEMA, "async_alarm_arm_night", - [SUPPORT_ALARM_ARM_NIGHT], + [AlarmControlPanelEntityFeature.ARM_NIGHT], ) component.async_register_entity_service( SERVICE_ALARM_ARM_VACATION, ALARM_SERVICE_SCHEMA, "async_alarm_arm_vacation", - [SUPPORT_ALARM_ARM_VACATION], + [AlarmControlPanelEntityFeature.ARM_VACATION], ) component.async_register_entity_service( SERVICE_ALARM_ARM_CUSTOM_BYPASS, ALARM_SERVICE_SCHEMA, "async_alarm_arm_custom_bypass", - [SUPPORT_ALARM_ARM_CUSTOM_BYPASS], + [AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS], ) component.async_register_entity_service( SERVICE_ALARM_TRIGGER, ALARM_SERVICE_SCHEMA, "async_alarm_trigger", - [SUPPORT_ALARM_TRIGGER], + [AlarmControlPanelEntityFeature.TRIGGER], ) return True @@ -129,12 +130,12 @@ class AlarmControlPanelEntity(Entity): entity_description: AlarmControlPanelEntityDescription _attr_changed_by: str | None = None _attr_code_arm_required: bool = True - _attr_code_format: str | None = None + _attr_code_format: CodeFormat | None = None _attr_supported_features: int @property - def code_format(self) -> str | None: - """Regex for code format or None if no code is required.""" + def code_format(self) -> CodeFormat | None: + """Code format or None if no code is required.""" return self._attr_code_format @property diff --git a/homeassistant/components/alarm_control_panel/const.py b/homeassistant/components/alarm_control_panel/const.py index f3688a27958..3a2f2c51551 100644 --- a/homeassistant/components/alarm_control_panel/const.py +++ b/homeassistant/components/alarm_control_panel/const.py @@ -1,7 +1,41 @@ """Provides the constants needed for component.""" - +from enum import IntEnum from typing import Final +from homeassistant.backports.enum import StrEnum + +DOMAIN: Final = "alarm_control_panel" + +ATTR_CHANGED_BY: Final = "changed_by" +ATTR_CODE_ARM_REQUIRED: Final = "code_arm_required" + + +class CodeFormat(StrEnum): + """Code formats for the Alarm Control Panel.""" + + TEXT = "text" + NUMBER = "number" + + +# These constants are deprecated as of Home Assistant 2022.5 +# Please use the CodeFormat enum instead. +FORMAT_TEXT: Final = "text" +FORMAT_NUMBER: Final = "number" + + +class AlarmControlPanelEntityFeature(IntEnum): + """Supported features of the alarm control panel entity.""" + + ARM_HOME = 1 + ARM_AWAY = 2 + ARM_NIGHT = 4 + TRIGGER = 8 + ARM_CUSTOM_BYPASS = 16 + ARM_VACATION = 32 + + +# These constants are deprecated as of Home Assistant 2022.5 +# Please use the AlarmControlPanelEntityFeature enum instead. SUPPORT_ALARM_ARM_HOME: Final = 1 SUPPORT_ALARM_ARM_AWAY: Final = 2 SUPPORT_ALARM_ARM_NIGHT: Final = 4 diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index c37bddafcd3..2680b033b03 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -67,7 +67,7 @@ async def async_get_actions( supported_features = get_supported_features(hass, entry.entity_id) - base_action = { + base_action: dict = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index b16490357d1..991d588eccf 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -2,13 +2,9 @@ import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - FORMAT_NUMBER, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -81,9 +77,11 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity): _attr_name = "Alarm Panel" _attr_should_poll = False - _attr_code_format = FORMAT_NUMBER + _attr_code_format = CodeFormat.NUMBER _attr_supported_features = ( - SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT ) def __init__(self, client, auto_bypass, code_arm_required, alt_night_mode): diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index db122e23f13..5b675779a22 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -12,11 +12,9 @@ from homeassistant.components import ( timer, vacuum, ) -from homeassistant.components.alarm_control_panel import FORMAT_NUMBER -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntityFeature, + CodeFormat, ) import homeassistant.components.climate.const as climate import homeassistant.components.media_player.const as media_player @@ -75,8 +73,6 @@ class AlexaCapability: https://developer.amazon.com/docs/device-apis/message-guide.html """ - # pylint: disable=no-self-use - supported_locales = {"en-US"} def __init__(self, entity: State, instance: str | None = None) -> None: @@ -701,7 +697,7 @@ class AlexaSpeaker(AlexaCapability): properties = [{"name": "volume"}] supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & media_player.SUPPORT_VOLUME_MUTE: + if supported & media_player.MediaPlayerEntityFeature.VOLUME_MUTE: properties.append({"name": "muted"}) return properties @@ -774,11 +770,11 @@ class AlexaPlaybackController(AlexaCapability): supported_features = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) operations = { - media_player.SUPPORT_NEXT_TRACK: "Next", - media_player.SUPPORT_PAUSE: "Pause", - media_player.SUPPORT_PLAY: "Play", - media_player.SUPPORT_PREVIOUS_TRACK: "Previous", - media_player.SUPPORT_STOP: "Stop", + media_player.MediaPlayerEntityFeature.NEXT_TRACK: "Next", + media_player.MediaPlayerEntityFeature.PAUSE: "Pause", + media_player.MediaPlayerEntityFeature.PLAY: "Play", + media_player.MediaPlayerEntityFeature.PREVIOUS_TRACK: "Previous", + media_player.MediaPlayerEntityFeature.STOP: "Stop", } return [ @@ -1023,9 +1019,9 @@ class AlexaThermostatController(AlexaCapability): """Return what properties this entity supports.""" properties = [{"name": "thermostatMode"}] supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & climate.SUPPORT_TARGET_TEMPERATURE: + if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE: properties.append({"name": "targetSetpoint"}) - if supported & climate.SUPPORT_TARGET_TEMPERATURE_RANGE: + if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE: properties.append({"name": "lowerSetpoint"}) properties.append({"name": "upperSetpoint"}) return properties @@ -1219,16 +1215,16 @@ class AlexaSecurityPanelController(AlexaCapability): configuration = {} supported_arm_states = [{"value": "DISARMED"}] - if supported & SUPPORT_ALARM_ARM_AWAY: + if supported & AlarmControlPanelEntityFeature.ARM_AWAY: supported_arm_states.append({"value": "ARMED_AWAY"}) - if supported & SUPPORT_ALARM_ARM_HOME: + if supported & AlarmControlPanelEntityFeature.ARM_HOME: supported_arm_states.append({"value": "ARMED_STAY"}) - if supported & SUPPORT_ALARM_ARM_NIGHT: + if supported & AlarmControlPanelEntityFeature.ARM_NIGHT: supported_arm_states.append({"value": "ARMED_NIGHT"}) configuration["supportedArmStates"] = supported_arm_states - if code_format == FORMAT_NUMBER: + if code_format == CodeFormat.NUMBER: configuration["supportedAuthorizationTypes"] = [{"type": "FOUR_DIGIT_PIN"}] return configuration @@ -1392,7 +1388,7 @@ class AlexaModeController(AlexaCapability): self._semantics = AlexaSemantics() # Add open/close semantics if tilt is not supported. - if not supported & cover.SUPPORT_SET_TILT_POSITION: + if not supported & cover.CoverEntityFeature.SET_TILT_POSITION: lower_labels.append(AlexaSemantics.ACTION_CLOSE) raise_labels.append(AlexaSemantics.ACTION_OPEN) self._semantics.add_states_to_value( @@ -1494,7 +1490,7 @@ class AlexaRangeController(AlexaCapability): # Fan speed percentage if self.instance == f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}": supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported and fan.SUPPORT_SET_SPEED: + if supported and fan.FanEntityFeature.SET_SPEED: return self.entity.attributes.get(fan.ATTR_PERCENTAGE) return 100 if self.entity.state == fan.STATE_ON else 0 @@ -1615,7 +1611,7 @@ class AlexaRangeController(AlexaCapability): self._semantics = AlexaSemantics() # Add open/close semantics if tilt is not supported. - if not supported & cover.SUPPORT_SET_TILT_POSITION: + if not supported & cover.CoverEntityFeature.SET_TILT_POSITION: lower_labels.append(AlexaSemantics.ACTION_CLOSE) raise_labels.append(AlexaSemantics.ACTION_OPEN) self._semantics.add_states_to_value( diff --git a/homeassistant/components/alexa/config.py b/homeassistant/components/alexa/config.py index 613fd948366..b6cbe6ba74b 100644 --- a/homeassistant/components/alexa/config.py +++ b/homeassistant/components/alexa/config.py @@ -84,7 +84,6 @@ class AbstractConfig(ABC): @callback def should_expose(self, entity_id): """If an entity should be exposed.""" - # pylint: disable=no-self-use return False @callback diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index 0532c85dac1..6b509d9b3c6 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -28,7 +28,9 @@ ATTR_REDIRECTION_URL = "redirectionURL" SYN_RESOLUTION_MATCH = "ER_SUCCESS_MATCH" -DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.0Z" +# Alexa requires timestamps to be formatted according to ISO 8601, YYYY-MM-DDThh:mm:ssZ +# https://developer.amazon.com/es-ES/docs/alexa/device-apis/alexa-scenecontroller.html#activate-response-event +DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" API_DIRECTIVE = "directive" API_ENDPOINT = "endpoint" diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 5ecd326afb6..9ee4ad3411f 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -501,15 +501,17 @@ class CoverCapabilities(AlexaEntity): yield AlexaPowerController(self.entity) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & cover.SUPPORT_SET_POSITION: + if supported & cover.CoverEntityFeature.SET_POSITION: yield AlexaRangeController( self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}" ) - elif supported & (cover.SUPPORT_CLOSE | cover.SUPPORT_OPEN): + elif supported & ( + cover.CoverEntityFeature.CLOSE | cover.CoverEntityFeature.OPEN + ): yield AlexaModeController( self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}" ) - if supported & cover.SUPPORT_SET_TILT_POSITION: + if supported & cover.CoverEntityFeature.SET_TILT_POSITION: yield AlexaRangeController(self.entity, instance=f"{cover.DOMAIN}.tilt") yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) @@ -552,17 +554,17 @@ class FanCapabilities(AlexaEntity): yield AlexaPowerController(self.entity) force_range_controller = True supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & fan.SUPPORT_OSCILLATE: + if supported & fan.FanEntityFeature.OSCILLATE: yield AlexaToggleController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}" ) force_range_controller = False - if supported & fan.SUPPORT_PRESET_MODE: + if supported & fan.FanEntityFeature.PRESET_MODE: yield AlexaModeController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}" ) force_range_controller = False - if supported & fan.SUPPORT_DIRECTION: + if supported & fan.FanEntityFeature.DIRECTION: yield AlexaModeController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}" ) @@ -572,7 +574,7 @@ class FanCapabilities(AlexaEntity): # For fans which only support on/off, no controller is added. This makes the # fan impossible to turn on or off through Alexa, most likely due to a bug in Alexa. # As a workaround, we add a range controller which can only be set to 0% or 100%. - if force_range_controller or supported & fan.SUPPORT_SET_SPEED: + if force_range_controller or supported & fan.FanEntityFeature.SET_SPEED: yield AlexaRangeController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}" ) @@ -615,26 +617,26 @@ class MediaPlayerCapabilities(AlexaEntity): yield AlexaPowerController(self.entity) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & media_player.const.SUPPORT_VOLUME_SET: + if supported & media_player.MediaPlayerEntityFeature.VOLUME_SET: yield AlexaSpeaker(self.entity) - elif supported & media_player.const.SUPPORT_VOLUME_STEP: + elif supported & media_player.MediaPlayerEntityFeature.VOLUME_STEP: yield AlexaStepSpeaker(self.entity) playback_features = ( - media_player.const.SUPPORT_PLAY - | media_player.const.SUPPORT_PAUSE - | media_player.const.SUPPORT_STOP - | media_player.const.SUPPORT_NEXT_TRACK - | media_player.const.SUPPORT_PREVIOUS_TRACK + media_player.MediaPlayerEntityFeature.PLAY + | media_player.MediaPlayerEntityFeature.PAUSE + | media_player.MediaPlayerEntityFeature.STOP + | media_player.MediaPlayerEntityFeature.NEXT_TRACK + | media_player.MediaPlayerEntityFeature.PREVIOUS_TRACK ) if supported & playback_features: yield AlexaPlaybackController(self.entity) yield AlexaPlaybackStateReporter(self.entity) - if supported & media_player.const.SUPPORT_SEEK: + if supported & media_player.MediaPlayerEntityFeature.SEEK: yield AlexaSeekController(self.entity) - if supported & media_player.SUPPORT_SELECT_SOURCE: + if supported & media_player.MediaPlayerEntityFeature.SELECT_SOURCE: inputs = AlexaInputController.get_valid_inputs( self.entity.attributes.get( media_player.const.ATTR_INPUT_SOURCE_LIST, [] @@ -643,14 +645,14 @@ class MediaPlayerCapabilities(AlexaEntity): if len(inputs) > 0: yield AlexaInputController(self.entity) - if supported & media_player.const.SUPPORT_PLAY_MEDIA: + if supported & media_player.MediaPlayerEntityFeature.PLAY_MEDIA: yield AlexaChannelController(self.entity) # AlexaEqualizerController is disabled for denonavr # since it blocks alexa from discovering any devices. domain = entity_sources(self.hass).get(self.entity_id, {}).get("domain") if ( - supported & media_player.const.SUPPORT_SELECT_SOUND_MODE + supported & media_player.MediaPlayerEntityFeature.SELECT_SOUND_MODE and domain != "denonavr" ): inputs = AlexaEqualizerController.get_valid_inputs( @@ -861,20 +863,21 @@ class VacuumCapabilities(AlexaEntity): """Yield the supported interfaces.""" supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if ( - (supported & vacuum.SUPPORT_TURN_ON) or (supported & vacuum.SUPPORT_START) + (supported & vacuum.VacuumEntityFeature.TURN_ON) + or (supported & vacuum.VacuumEntityFeature.START) ) and ( - (supported & vacuum.SUPPORT_TURN_OFF) - or (supported & vacuum.SUPPORT_RETURN_HOME) + (supported & vacuum.VacuumEntityFeature.TURN_OFF) + or (supported & vacuum.VacuumEntityFeature.RETURN_HOME) ): yield AlexaPowerController(self.entity) - if supported & vacuum.SUPPORT_FAN_SPEED: + if supported & vacuum.VacuumEntityFeature.FAN_SPEED: yield AlexaRangeController( self.entity, instance=f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}" ) - if supported & vacuum.SUPPORT_PAUSE: - support_resume = bool(supported & vacuum.SUPPORT_START) + if supported & vacuum.VacuumEntityFeature.PAUSE: + support_resume = bool(supported & vacuum.VacuumEntityFeature.START) yield AlexaTimeHoldController( self.entity, allow_remote_resume=support_resume ) @@ -895,7 +898,7 @@ class CameraCapabilities(AlexaEntity): """Yield the supported interfaces.""" if self._check_requirements(): supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported & camera.SUPPORT_STREAM: + if supported & camera.CameraEntityFeature.STREAM: yield AlexaCameraStreamController(self.entity) yield AlexaEndpointHealth(self.hass, self.entity) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index a27bc432b4f..f9162026fe8 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -129,13 +129,19 @@ async def async_api_turn_on(hass, config, directive, context): service = fan.SERVICE_TURN_ON elif domain == vacuum.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if not supported & vacuum.SUPPORT_TURN_ON and supported & vacuum.SUPPORT_START: + if ( + not supported & vacuum.VacuumEntityFeature.TURN_ON + and supported & vacuum.VacuumEntityFeature.START + ): service = vacuum.SERVICE_START elif domain == timer.DOMAIN: service = timer.SERVICE_START elif domain == media_player.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF + power_features = ( + media_player.MediaPlayerEntityFeature.TURN_ON + | media_player.MediaPlayerEntityFeature.TURN_OFF + ) if not supported & power_features: service = media_player.SERVICE_MEDIA_PLAY @@ -166,15 +172,18 @@ async def async_api_turn_off(hass, config, directive, context): elif domain == vacuum.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if ( - not supported & vacuum.SUPPORT_TURN_OFF - and supported & vacuum.SUPPORT_RETURN_HOME + not supported & vacuum.VacuumEntityFeature.TURN_OFF + and supported & vacuum.VacuumEntityFeature.RETURN_HOME ): service = vacuum.SERVICE_RETURN_TO_BASE elif domain == timer.DOMAIN: service = timer.SERVICE_CANCEL elif domain == media_player.DOMAIN: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF + power_features = ( + media_player.MediaPlayerEntityFeature.TURN_ON + | media_player.MediaPlayerEntityFeature.TURN_OFF + ) if not supported & power_features: service = media_player.SERVICE_MEDIA_STOP @@ -1087,7 +1096,7 @@ async def async_api_set_range(hass, config, directive, context): service = fan.SERVICE_TURN_OFF else: supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if supported and fan.SUPPORT_SET_SPEED: + if supported and fan.FanEntityFeature.SET_SPEED: service = fan.SERVICE_SET_PERCENTAGE data[fan.ATTR_PERCENTAGE] = range_value else: diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index aec43f41f9a..fb207f17ff4 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -200,8 +200,6 @@ class AlexaCapabilityResource: https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources """ - # pylint: disable=no-self-use - def __init__(self, labels): """Initialize an Alexa resource.""" self._resource_labels = [] diff --git a/homeassistant/components/almond/manifest.json b/homeassistant/components/almond/manifest.json index 94203b46752..012450180ca 100644 --- a/homeassistant/components/almond/manifest.json +++ b/homeassistant/components/almond/manifest.json @@ -3,7 +3,7 @@ "name": "Almond", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/almond", - "dependencies": ["http", "conversation"], + "dependencies": ["auth", "conversation"], "codeowners": ["@gcampax", "@balloob"], "requirements": ["pyalmond==0.0.2"], "iot_class": "local_polling", diff --git a/homeassistant/components/almond/translations/hu.json b/homeassistant/components/almond/translations/hu.json index b0a3a1b2461..b424675faaa 100644 --- a/homeassistant/components/almond/translations/hu.json +++ b/homeassistant/components/almond/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { @@ -12,7 +12,7 @@ "title": "Almond - Home Assistant b\u0151v\u00edtm\u00e9nnyel" }, "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/almond/translations/it.json b/homeassistant/components/almond/translations/it.json index 58eadad0d80..1d028e73111 100644 --- a/homeassistant/components/almond/translations/it.json +++ b/homeassistant/components/almond/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "cannot_connect": "Impossibile connettersi", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, diff --git a/homeassistant/components/alpha_vantage/manifest.json b/homeassistant/components/alpha_vantage/manifest.json index b608d18bb7a..47930322787 100644 --- a/homeassistant/components/alpha_vantage/manifest.json +++ b/homeassistant/components/alpha_vantage/manifest.json @@ -3,7 +3,7 @@ "name": "Alpha Vantage", "documentation": "https://www.home-assistant.io/integrations/alpha_vantage", "requirements": ["alpha_vantage==2.3.1"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_polling", "loggers": ["alpha_vantage"] } diff --git a/homeassistant/components/ambee/translations/hu.json b/homeassistant/components/ambee/translations/hu.json index e4cef44c5ba..80b14ac7470 100644 --- a/homeassistant/components/ambee/translations/hu.json +++ b/homeassistant/components/ambee/translations/hu.json @@ -19,7 +19,7 @@ "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Integr\u00e1lja \u00f6ssze Ambeet Home Assistanttal." } diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index c419aa0560c..0bf4ec35526 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -9,11 +9,7 @@ import ambiclimate import voluptuous as vol from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_NAME, @@ -41,8 +37,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE - SEND_COMFORT_FEEDBACK_SCHEMA = vol.Schema( {vol.Required(ATTR_NAME): cv.string, vol.Required(ATTR_VALUE): cv.string} ) @@ -154,8 +148,8 @@ class AmbiclimateEntity(ClimateEntity): _attr_temperature_unit = TEMP_CELSIUS _attr_target_temperature_step = 1 - _attr_supported_features = SUPPORT_FLAGS - _attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] def __init__(self, heater, store): """Initialize the thermostat.""" @@ -177,10 +171,10 @@ class AmbiclimateEntity(ClimateEntity): async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: await self._heater.turn_on() return - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._heater.turn_off() async def async_update(self) -> None: @@ -201,5 +195,5 @@ class AmbiclimateEntity(ClimateEntity): self._attr_current_temperature = data.get("temperature") self._attr_current_humidity = data.get("humidity") self._attr_hvac_mode = ( - HVAC_MODE_HEAT if data.get("power", "").lower() == "on" else HVAC_MODE_OFF + HVACMode.HEAT if data.get("power", "").lower() == "on" else HVACMode.OFF ) diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 00fa339b0d8..e93c21ce5ba 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -140,7 +140,6 @@ class AmbiclimateAuthCallbackView(HomeAssistantView): async def get(self, request: web.Request) -> str: """Receive authorization token.""" - # pylint: disable=no-self-use if (code := request.query.get("code")) is None: return "No code" hass = request.app["hass"] diff --git a/homeassistant/components/ambiclimate/translations/hu.json b/homeassistant/components/ambiclimate/translations/hu.json index 1e67873f1aa..3de93421f42 100644 --- a/homeassistant/components/ambiclimate/translations/hu.json +++ b/homeassistant/components/ambiclimate/translations/hu.json @@ -9,12 +9,12 @@ "default": "Sikeres hiteles\u00edt\u00e9s" }, "error": { - "follow_link": "K\u00e9rem, k\u00f6vesse a hivatkoz\u00e1st \u00e9s hiteles\u00edtse mag\u00e1t miel\u0151tt megnyomn\u00e1 a K\u00fcld\u00e9s gombot", + "follow_link": "K\u00e9rem, k\u00f6vesse a hivatkoz\u00e1st \u00e9s hiteles\u00edtse mag\u00e1t miel\u0151tt folytatn\u00e1", "no_token": "Ambiclimate-al nem siker\u00fclt a hiteles\u00edt\u00e9s" }, "step": { "auth": { - "description": "K\u00e9rj\u00fck, k\u00f6vesse ezt a [link]({authorization_url}}) \u00e9s ** Enged\u00e9lyezze ** a hozz\u00e1f\u00e9r\u00e9st Ambiclimate -fi\u00f3kj\u00e1hoz, majd t\u00e9rjen vissza, \u00e9s nyomja meg az al\u00e1bbi ** K\u00fcld\u00e9s ** gombot.\n(Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a megadott visszah\u00edv\u00e1si URL {cb_url})", + "description": "K\u00e9rj\u00fck, k\u00f6vesse ezt a [link]({authorization_url}}) \u00e9s **Enged\u00e9lyezze** a hozz\u00e1f\u00e9r\u00e9st Ambiclimate -fi\u00f3kj\u00e1hoz, majd t\u00e9rjen vissza, \u00e9s nyomja meg az al\u00e1bbi **Mehet** gombot.\n(Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a megadott visszah\u00edv\u00e1si URL {cb_url})", "title": "Ambiclimate hiteles\u00edt\u00e9se" } } diff --git a/homeassistant/components/ambiclimate/translations/it.json b/homeassistant/components/ambiclimate/translations/it.json index ccf0836ee1d..d48f6748bbe 100644 --- a/homeassistant/components/ambiclimate/translations/it.json +++ b/homeassistant/components/ambiclimate/translations/it.json @@ -3,13 +3,13 @@ "abort": { "access_token": "Errore sconosciuto durante la generazione di un token di accesso.", "already_configured": "L'account \u00e8 gi\u00e0 configurato", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione." }, "create_entry": { "default": "Autenticazione riuscita" }, "error": { - "follow_link": "Si prega di seguire il link e di autenticarsi prima di premere Invia", + "follow_link": "Segui il collegamento e autenticati prima di premere Invia", "no_token": "Non autenticato con Ambiclimate" }, "step": { diff --git a/homeassistant/components/ambient_station/translations/hu.json b/homeassistant/components/ambient_station/translations/hu.json index 6974bda2c20..bc2ff29b7ee 100644 --- a/homeassistant/components/ambient_station/translations/hu.json +++ b/homeassistant/components/ambient_station/translations/hu.json @@ -5,7 +5,7 @@ }, "error": { "invalid_key": "\u00c9rv\u00e9nytelen API kulcs", - "no_devices": "Nincs a fi\u00f3kodban tal\u00e1lhat\u00f3 eszk\u00f6z" + "no_devices": "Nem tal\u00e1lhat\u00f3 a fi\u00f3kj\u00e1ban eszk\u00f6z" }, "step": { "user": { diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index f2472575259..e3f48263e8b 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -7,6 +7,7 @@ from contextlib import asynccontextmanager, suppress from dataclasses import dataclass from datetime import datetime, timedelta import logging +import threading from typing import Any import aiohttp @@ -30,15 +31,14 @@ from homeassistant.const import ( CONF_USERNAME, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE, - EVENT_HOMEASSISTANT_STOP, HTTP_BASIC_AUTHENTICATION, Platform, ) -from homeassistant.core import Event, HomeAssistant, ServiceCall, callback +from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import Unauthorized, UnknownUser from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.service import async_extract_entity_ids from homeassistant.helpers.typing import ConfigType @@ -144,10 +144,13 @@ class AmcrestChecker(ApiWrapper): self._hass = hass self._wrap_name = name self._wrap_errors = 0 - self._wrap_lock = asyncio.Lock() + self._wrap_lock = threading.Lock() + self._async_wrap_lock = asyncio.Lock() self._wrap_login_err = False - self._wrap_event_flag = asyncio.Event() + self._wrap_event_flag = threading.Event() self._wrap_event_flag.set() + self._async_wrap_event_flag = asyncio.Event() + self._async_wrap_event_flag.set() self._unsub_recheck: Callable[[], None] | None = None super().__init__( host, @@ -164,12 +167,18 @@ class AmcrestChecker(ApiWrapper): return self._wrap_errors <= MAX_ERRORS and not self._wrap_login_err @property - def available_flag(self) -> asyncio.Event: + def available_flag(self) -> threading.Event: """Return event flag that indicates if camera's API is responding.""" return self._wrap_event_flag + @property + def async_available_flag(self) -> asyncio.Event: + """Return event flag that indicates if camera's API is responding.""" + return self._async_wrap_event_flag + def _start_recovery(self) -> None: - self._wrap_event_flag.clear() + self.available_flag.clear() + self.async_available_flag.clear() async_dispatcher_send( self._hass, service_signal(SERVICE_UPDATE, self._wrap_name) ) @@ -177,9 +186,22 @@ class AmcrestChecker(ApiWrapper): self._hass, self._wrap_test_online, RECHECK_INTERVAL ) + def command(self, *args: Any, **kwargs: Any) -> Any: + """amcrest.ApiWrapper.command wrapper to catch errors.""" + try: + ret = super().command(*args, **kwargs) + except LoginError as ex: + self._handle_offline(ex) + raise + except AmcrestError: + self._handle_error() + raise + self._set_online() + return ret + async def async_command(self, *args: Any, **kwargs: Any) -> httpx.Response: """amcrest.ApiWrapper.command wrapper to catch errors.""" - async with self._command_wrapper(): + async with self._async_command_wrapper(): ret = await super().async_command(*args, **kwargs) return ret @@ -188,35 +210,47 @@ class AmcrestChecker(ApiWrapper): self, *args: Any, **kwargs: Any ) -> AsyncIterator[httpx.Response]: """amcrest.ApiWrapper.command wrapper to catch errors.""" - async with self._command_wrapper(): + async with self._async_command_wrapper(): async with super().async_stream_command(*args, **kwargs) as ret: yield ret @asynccontextmanager - async def _command_wrapper(self) -> AsyncIterator[None]: + async def _async_command_wrapper(self) -> AsyncIterator[None]: try: yield except LoginError as ex: - async with self._wrap_lock: - was_online = self.available - was_login_err = self._wrap_login_err - self._wrap_login_err = True - if not was_login_err: - _LOGGER.error("%s camera offline: Login error: %s", self._wrap_name, ex) - if was_online: - self._start_recovery() + async with self._async_wrap_lock: + self._handle_offline(ex) raise except AmcrestError: - async with self._wrap_lock: - was_online = self.available - errs = self._wrap_errors = self._wrap_errors + 1 - offline = not self.available - _LOGGER.debug("%s camera errs: %i", self._wrap_name, errs) - if was_online and offline: - _LOGGER.error("%s camera offline: Too many errors", self._wrap_name) - self._start_recovery() + async with self._async_wrap_lock: + self._handle_error() raise - async with self._wrap_lock: + async with self._async_wrap_lock: + self._set_online() + + def _handle_offline(self, ex: Exception) -> None: + with self._wrap_lock: + was_online = self.available + was_login_err = self._wrap_login_err + self._wrap_login_err = True + if not was_login_err: + _LOGGER.error("%s camera offline: Login error: %s", self._wrap_name, ex) + if was_online: + self._start_recovery() + + def _handle_error(self) -> None: + with self._wrap_lock: + was_online = self.available + errs = self._wrap_errors = self._wrap_errors + 1 + offline = not self.available + _LOGGER.debug("%s camera errs: %i", self._wrap_name, errs) + if was_online and offline: + _LOGGER.error("%s camera offline: Too many errors", self._wrap_name) + self._start_recovery() + + def _set_online(self) -> None: + with self._wrap_lock: was_offline = not self.available self._wrap_errors = 0 self._wrap_login_err = False @@ -225,7 +259,8 @@ class AmcrestChecker(ApiWrapper): self._unsub_recheck() self._unsub_recheck = None _LOGGER.error("%s camera back online", self._wrap_name) - self._wrap_event_flag.set() + self.available_flag.set() + self.async_available_flag.set() async_dispatcher_send( self._hass, service_signal(SERVICE_UPDATE, self._wrap_name) ) @@ -237,18 +272,18 @@ class AmcrestChecker(ApiWrapper): await self.async_current_time -async def _monitor_events( +def _monitor_events( hass: HomeAssistant, name: str, api: AmcrestChecker, event_codes: set[str], ) -> None: while True: - await api.available_flag.wait() + api.available_flag.wait() try: - async for code, payload in api.async_event_actions("All"): + for code, payload in api.event_actions("All"): event_data = {"camera": name, "event": code, "payload": payload} - hass.bus.async_fire("amcrest", event_data) + hass.bus.fire("amcrest", event_data) if code in event_codes: signal = service_signal(SERVICE_EVENT, name, code) start = any( @@ -256,18 +291,32 @@ async def _monitor_events( for key, val in payload.items() ) _LOGGER.debug("Sending signal: '%s': %s", signal, start) - async_dispatcher_send(hass, signal, start) + dispatcher_send(hass, signal, start) except AmcrestError as error: _LOGGER.warning( "Error while processing events from %s camera: %r", name, error ) +def _start_event_monitor( + hass: HomeAssistant, + name: str, + api: AmcrestChecker, + event_codes: set[str], +) -> None: + thread = threading.Thread( + target=_monitor_events, + name=f"Amcrest {name}", + args=(hass, name, api, event_codes), + daemon=True, + ) + thread.start() + + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Amcrest IP Camera component.""" hass.data.setdefault(DATA_AMCREST, {DEVICES: {}, CAMERAS: []}) - monitor_tasks = [] for device in config[DOMAIN]: name: str = device[CONF_NAME] username: str = device[CONF_USERNAME] @@ -328,9 +377,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: and sensor.event_code is not None } - monitor_tasks.append( - asyncio.create_task(_monitor_events(hass, name, api, event_codes)) - ) + _start_event_monitor(hass, name, api, event_codes) if sensors: hass.async_create_task( @@ -354,13 +401,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) - @callback - def cancel_monitors(event: Event) -> None: - for monitor_task in monitor_tasks: - monitor_task.cancel() - - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, cancel_monitors) - if not hass.data[DATA_AMCREST][DEVICES]: return False diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 61b97b041cd..c3d1e0d28d6 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -12,7 +12,7 @@ from amcrest import AmcrestError from haffmpeg.camera import CameraMjpeg import voluptuous as vol -from homeassistant.components.camera import SUPPORT_ON_OFF, SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.camera.const import DOMAIN as CAMERA_DOMAIN from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON @@ -164,6 +164,8 @@ class AmcrestCommandFailed(Exception): class AmcrestCam(Camera): """An implementation of an Amcrest IP camera.""" + _attr_supported_features = CameraEntityFeature.ON_OFF | CameraEntityFeature.STREAM + def __init__(self, name: str, device: AmcrestDevice, ffmpeg: FFmpegManager) -> None: """Initialize an Amcrest camera.""" super().__init__() @@ -311,11 +313,6 @@ class AmcrestCam(Camera): """Return True if entity is available.""" return self._api.available - @property - def supported_features(self) -> int: - """Return supported features.""" - return SUPPORT_ON_OFF | SUPPORT_STREAM - # Camera property overrides @property diff --git a/homeassistant/components/androidtv/diagnostics.py b/homeassistant/components/androidtv/diagnostics.py index 0a4245c40dc..0921fecc500 100644 --- a/homeassistant/components/androidtv/diagnostics.py +++ b/homeassistant/components/androidtv/diagnostics.py @@ -5,7 +5,7 @@ from typing import Any import attr -from homeassistant.components.diagnostics import REDACTED, async_redact_data +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CONNECTIONS, ATTR_IDENTIFIERS, CONF_UNIQUE_ID from homeassistant.core import HomeAssistant @@ -14,7 +14,8 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from .const import ANDROID_DEV, DOMAIN, PROP_ETHMAC, PROP_SERIALNO, PROP_WIFIMAC TO_REDACT = {CONF_UNIQUE_ID} # UniqueID contain MAC Address -TO_REDACT_DEV = {PROP_ETHMAC, PROP_SERIALNO, PROP_WIFIMAC} +TO_REDACT_DEV = {ATTR_CONNECTIONS, ATTR_IDENTIFIERS} +TO_REDACT_DEV_PROP = {PROP_ETHMAC, PROP_SERIALNO, PROP_WIFIMAC} async def async_get_config_entry_diagnostics( @@ -26,13 +27,10 @@ async def async_get_config_entry_diagnostics( # Get information from AndroidTV library aftv = hass_data[ANDROID_DEV] - data_dev = {"device_class": aftv.DEVICE_CLASS} - for prop, value in aftv.device_properties.items(): - if prop in TO_REDACT_DEV and value: - data_dev[prop] = REDACTED - else: - data_dev[prop] = value - data["device_properties"] = data_dev + data["device_properties"] = { + **async_redact_data(aftv.device_properties, TO_REDACT_DEV_PROP), + "device_class": aftv.DEVICE_CLASS, + } # Gather information how this AndroidTV device is represented in Home Assistant device_registry = dr.async_get(hass) @@ -44,12 +42,9 @@ async def async_get_config_entry_diagnostics( return data data["device"] = { - **attr.asdict(hass_device), + **async_redact_data(attr.asdict(hass_device), TO_REDACT_DEV), "entities": {}, } - data["device"][ATTR_IDENTIFIERS] = REDACTED - if ATTR_CONNECTIONS in data["device"]: - data["device"][ATTR_CONNECTIONS] = REDACTED hass_entities = er.async_entries_for_device( entity_registry, @@ -67,15 +62,14 @@ async def async_get_config_entry_diagnostics( # The context doesn't provide useful information in this case. state_dict.pop("context", None) - entity_dict = async_redact_data( - { - **attr.asdict( + data["device"]["entities"][entity_entry.entity_id] = { + **async_redact_data( + attr.asdict( entity_entry, filter=lambda attr, value: attr.name != "entity_id" ), - "state": state_dict, - }, - TO_REDACT, - ) - data["device"]["entities"][entity_entry.entity_id] = entity_dict + TO_REDACT, + ), + "state": state_dict, + } return data diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 729cac082c7..7bddd13c833 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ "adb-shell[async]==0.4.2", - "androidtv[async]==0.0.66", + "androidtv[async]==0.0.67", "pure-python-adb[async]==0.3.0.dev0" ], "codeowners": ["@JeffLIrion", "@ollo69"], diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index db09dc8ec66..d2a0b97ed6b 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -17,19 +17,9 @@ from androidtv.exceptions import LockNotAcquiredException import voluptuous as vol from homeassistant.components import persistent_notification -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -73,31 +63,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -SUPPORT_ANDROIDTV = ( - SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_STOP - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP -) - -SUPPORT_FIRETV = ( - SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_STOP -) - ATTR_ADB_RESPONSE = "adb_response" ATTR_DEVICE_PATH = "device_path" ATTR_HDMI_INPUT = "hdmi_input" @@ -108,7 +73,8 @@ SERVICE_DOWNLOAD = "download" SERVICE_LEARN_SENDEVENT = "learn_sendevent" SERVICE_UPLOAD = "upload" -DEFAULT_NAME = "Android TV" +PREFIX_ANDROIDTV = "Android TV" +PREFIX_FIRETV = "Fire TV" # Translate from `AndroidTV` / `FireTV` reported state to HA state. ANDROIDTV_STATES = { @@ -128,7 +94,9 @@ async def async_setup_entry( """Set up the Android TV entity.""" aftv = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV] device_class = aftv.DEVICE_CLASS - device_type = "Android TV" if device_class == DEVICE_ANDROIDTV else "Fire TV" + device_type = ( + PREFIX_ANDROIDTV if device_class == DEVICE_ANDROIDTV else PREFIX_FIRETV + ) # CONF_NAME may be present in entry.data for configuration imported from YAML device_name = entry.data.get(CONF_NAME) or f"{device_type} {entry.data[CONF_HOST]}" @@ -286,6 +254,9 @@ class ADBDevice(MediaPlayerEntity): ATTR_HDMI_INPUT: None, } + # The number of consecutive failed connect attempts + self._failed_connect_count = 0 + def _process_config(self): """Load the config options.""" _LOGGER.debug("Loading configuration options") @@ -467,15 +438,31 @@ class ADBDevice(MediaPlayerEntity): class AndroidTVDevice(ADBDevice): """Representation of an Android TV device.""" - _attr_supported_features = SUPPORT_ANDROIDTV + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + ) @adb_decorator(override_available=True) async def async_update(self): """Update the device state and, if necessary, re-connect.""" # Check if device is disconnected. - if not self.available: + if not self._attr_available: # Try to connect - self._attr_available = await self.aftv.adb_connect(always_log_errors=False) + if await self.aftv.adb_connect(log_errors=self._failed_connect_count == 0): + self._failed_connect_count = 0 + self._attr_available = True + else: + self._failed_connect_count += 1 # If the ADB connection is not intact, don't update. if not self.available: @@ -543,15 +530,28 @@ class AndroidTVDevice(ADBDevice): class FireTVDevice(ADBDevice): """Representation of a Fire TV device.""" - _attr_supported_features = SUPPORT_FIRETV + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.STOP + ) @adb_decorator(override_available=True) async def async_update(self): """Update the device state and, if necessary, re-connect.""" # Check if device is disconnected. - if not self.available: + if not self._attr_available: # Try to connect - self._attr_available = await self.aftv.adb_connect(always_log_errors=False) + if await self.aftv.adb_connect(log_errors=self._failed_connect_count == 0): + self._failed_connect_count = 0 + self._attr_available = True + else: + self._failed_connect_count += 1 # If the ADB connection is not intact, don't update. if not self.available: diff --git a/homeassistant/components/androidtv/translations/hu.json b/homeassistant/components/androidtv/translations/hu.json index c8d8c0fd97b..3b75153a351 100644 --- a/homeassistant/components/androidtv/translations/hu.json +++ b/homeassistant/components/androidtv/translations/hu.json @@ -43,12 +43,12 @@ "init": { "data": { "apps": "Alkalmaz\u00e1slista konfigur\u00e1l\u00e1sa", - "exclude_unnamed_apps": "Ismeretlen nev\u0171 alkalmaz\u00e1s kiz\u00e1r\u00e1sa", - "get_sources": "A fut\u00f3 alkalmaz\u00e1sok forr\u00e1slist\u00e1jak\u00e9nt val\u00f3 megjelen\u00edt\u00e9se", - "screencap": "A k\u00e9perny\u0151n megjelen\u0151 k\u00e9p legyen-e az albumbor\u00edt\u00f3", + "exclude_unnamed_apps": "Az ismeretlen nev\u0171 alkalmaz\u00e1sok kiz\u00e1r\u00e1sa a forr\u00e1slist\u00e1b\u00f3l", + "get_sources": "A fut\u00f3 alkalmaz\u00e1sok megjelen\u00edt\u00e9se a bemeneti forr\u00e1sok list\u00e1j\u00e1ban", + "screencap": "Haszn\u00e1ljon k\u00e9perny\u0151felv\u00e9telt az albumbor\u00edt\u00f3khoz", "state_detection_rules": "\u00c1llapotfelismer\u00e9si szab\u00e1lyok konfigur\u00e1l\u00e1sa", - "turn_off_command": "ADB shell parancs az alap\u00e9rtelmezett kikapcsol\u00e1si (turn_off) parancs fel\u00fcl\u00edr\u00e1s\u00e1ra", - "turn_on_command": "ADB shell parancs az alap\u00e9rtelmezett bekapcsol\u00e1si (turn_on) parancs fel\u00fcl\u00edr\u00e1s\u00e1ra" + "turn_off_command": "ADB shell kikapcsol\u00e1si parancs (alap\u00e9rtelmez\u00e9s szerint hagyja \u00fcresen)", + "turn_on_command": "ADB shell bekapcsol\u00e1si parancs (alap\u00e9rtelmez\u00e9s szerint hagyja \u00fcresen)" }, "title": "Android TV be\u00e1ll\u00edt\u00e1sok" }, diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index 3044ddd7544..05bfea7ef45 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -6,13 +6,10 @@ import logging import anthemav import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -37,14 +34,6 @@ DOMAIN = "anthemav" DEFAULT_PORT = 14999 -SUPPORT_ANTHEMAV = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -92,7 +81,13 @@ class AnthemAVR(MediaPlayerEntity): """Entity reading values from Anthem AVR protocol.""" _attr_should_poll = False - _attr_supported_features = SUPPORT_ANTHEMAV + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) def __init__(self, avr, name): """Initialize entity with transport.""" diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 28744455aee..ab43632e25c 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -14,7 +14,6 @@ from homeassistant.bootstrap import DATA_LOGGING from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, - EVENT_TIME_CHANGED, MATCH_ALL, URL_API, URL_API_COMPONENTS, @@ -90,7 +89,6 @@ class APIEventStream(HomeAssistantView): async def get(self, request): """Provide a streaming interface for the event bus.""" - # pylint: disable=no-self-use if not request["hass_user"].is_admin: raise Unauthorized() hass = request.app["hass"] @@ -102,9 +100,6 @@ class APIEventStream(HomeAssistantView): async def forward_events(event): """Forward events to the open request.""" - if event.event_type == EVENT_TIME_CHANGED: - return - if restrict and event.event_type not in restrict: return @@ -383,7 +378,6 @@ class APIErrorLog(HomeAssistantView): async def get(self, request): """Retrieve API error log.""" - # pylint: disable=no-self-use if not request["hass_user"].is_admin: raise Unauthorized() return web.FileResponse(request.app["hass"].data[DATA_LOGGING]) diff --git a/homeassistant/components/apple_tv/browse_media.py b/homeassistant/components/apple_tv/browse_media.py index 3c0eee8b6ad..8d0a94ca858 100644 --- a/homeassistant/components/apple_tv/browse_media.py +++ b/homeassistant/components/apple_tv/browse_media.py @@ -18,7 +18,7 @@ def build_app_list(app_list): return BrowseMedia( media_class=MEDIA_CLASS_DIRECTORY, - media_content_id=None, + media_content_id="apps", media_content_type=MEDIA_TYPE_APPS, title="Apps", can_play=True, diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 1f8cabb1d14..6c1b35f70f8 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -13,7 +13,15 @@ from pyatv.const import ( ) from pyatv.helpers import is_streamable -from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity +from homeassistant.components import media_source +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) +from homeassistant.components.media_player.browse_media import ( + async_process_play_media_url, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_APP, MEDIA_TYPE_MUSIC, @@ -22,21 +30,6 @@ from homeassistant.components.media_player.const import ( REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -60,45 +53,49 @@ _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 0 # We always consider these to be supported -SUPPORT_BASE = SUPPORT_TURN_ON | SUPPORT_TURN_OFF +SUPPORT_BASE = MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF # This is the "optimistic" view of supported features and will be returned until the # actual set of supported feature have been determined (will always be all or a subset # of these). SUPPORT_APPLE_TV = ( SUPPORT_BASE - | SUPPORT_BROWSE_MEDIA - | SUPPORT_PLAY_MEDIA - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_SEEK - | SUPPORT_STOP - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_REPEAT_SET - | SUPPORT_SHUFFLE_SET + | MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SHUFFLE_SET ) # Map features in pyatv to Home Assistant SUPPORT_FEATURE_MAPPING = { - FeatureName.PlayUrl: SUPPORT_PLAY_MEDIA, - FeatureName.StreamFile: SUPPORT_PLAY_MEDIA, - FeatureName.Pause: SUPPORT_PAUSE, - FeatureName.Play: SUPPORT_PLAY, - FeatureName.SetPosition: SUPPORT_SEEK, - FeatureName.Stop: SUPPORT_STOP, - FeatureName.Next: SUPPORT_NEXT_TRACK, - FeatureName.Previous: SUPPORT_PREVIOUS_TRACK, - FeatureName.VolumeUp: SUPPORT_VOLUME_STEP, - FeatureName.VolumeDown: SUPPORT_VOLUME_STEP, - FeatureName.SetRepeat: SUPPORT_REPEAT_SET, - FeatureName.SetShuffle: SUPPORT_SHUFFLE_SET, - FeatureName.SetVolume: SUPPORT_VOLUME_SET, - FeatureName.AppList: SUPPORT_BROWSE_MEDIA | SUPPORT_SELECT_SOURCE, - FeatureName.LaunchApp: SUPPORT_BROWSE_MEDIA | SUPPORT_SELECT_SOURCE, + FeatureName.PlayUrl: MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.PLAY_MEDIA, + FeatureName.StreamFile: MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.PLAY_MEDIA, + FeatureName.Pause: MediaPlayerEntityFeature.PAUSE, + FeatureName.Play: MediaPlayerEntityFeature.PLAY, + FeatureName.SetPosition: MediaPlayerEntityFeature.SEEK, + FeatureName.Stop: MediaPlayerEntityFeature.STOP, + FeatureName.Next: MediaPlayerEntityFeature.NEXT_TRACK, + FeatureName.Previous: MediaPlayerEntityFeature.PREVIOUS_TRACK, + FeatureName.VolumeUp: MediaPlayerEntityFeature.VOLUME_STEP, + FeatureName.VolumeDown: MediaPlayerEntityFeature.VOLUME_STEP, + FeatureName.SetRepeat: MediaPlayerEntityFeature.REPEAT_SET, + FeatureName.SetShuffle: MediaPlayerEntityFeature.SHUFFLE_SET, + FeatureName.SetVolume: MediaPlayerEntityFeature.VOLUME_SET, + FeatureName.AppList: MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.SELECT_SOURCE, + FeatureName.LaunchApp: MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.SELECT_SOURCE, } @@ -285,12 +282,21 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity): # RAOP. Otherwise try to play it with regular AirPlay. if media_type == MEDIA_TYPE_APP: await self.atv.apps.launch_app(media_id) - elif self._is_feature_available(FeatureName.StreamFile) and ( - await is_streamable(media_id) or media_type == MEDIA_TYPE_MUSIC + + if media_source.is_media_source_id(media_id): + play_item = await media_source.async_resolve_media(self.hass, media_id) + media_id = play_item.url + media_type = MEDIA_TYPE_MUSIC + + media_id = async_process_play_media_url(self.hass, media_id) + + if self._is_feature_available(FeatureName.StreamFile) and ( + media_type == MEDIA_TYPE_MUSIC or await is_streamable(media_id) ): _LOGGER.debug("Streaming %s via RAOP", media_id) await self.atv.stream.stream_file(media_id) - elif self._is_feature_available(FeatureName.PlayUrl): + + if self._is_feature_available(FeatureName.PlayUrl): _LOGGER.debug("Playing %s via AirPlay", media_id) await self.atv.stream.play_url(media_id) else: @@ -389,7 +395,37 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity): media_content_id=None, ) -> BrowseMedia: """Implement the websocket media browsing helper.""" - return build_app_list(self._app_list) + if media_content_id == "apps" or ( + # If we can't stream files or URLs, we can't browse media. + # In that case the `BROWSE_MEDIA` feature was added because of AppList/LaunchApp + not self._is_feature_available(FeatureName.PlayUrl) + and not self._is_feature_available(FeatureName.StreamFile) + ): + return build_app_list(self._app_list) + + if self._app_list: + kwargs = {} + else: + # If it has no apps, assume it has no display + kwargs = { + "content_filter": lambda item: item.media_content_type.startswith( + "audio/" + ), + } + + cur_item = await media_source.async_browse_media( + self.hass, media_content_id, **kwargs + ) + + # If media content id is not None, we're browsing into a media source + if media_content_id is not None: + return cur_item + + # Add app item if we have one + if self._app_list and cur_item.children: + cur_item.children.insert(0, build_app_list(self._app_list)) + + return cur_item async def async_turn_on(self): """Turn the media player on.""" diff --git a/homeassistant/components/apple_tv/translations/bg.json b/homeassistant/components/apple_tv/translations/bg.json index 4629a79d152..b1923cab650 100644 --- a/homeassistant/components/apple_tv/translations/bg.json +++ b/homeassistant/components/apple_tv/translations/bg.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", "already_configured_device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", + "ipv6_not_supported": "IPv6 \u043d\u0435 \u0441\u0435 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430.", "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" diff --git a/homeassistant/components/apple_tv/translations/ca.json b/homeassistant/components/apple_tv/translations/ca.json index 88c49059067..c672733193a 100644 --- a/homeassistant/components/apple_tv/translations/ca.json +++ b/homeassistant/components/apple_tv/translations/ca.json @@ -9,6 +9,7 @@ "device_not_found": "No s'ha trobat el dispositiu durant el descobriment, prova de tornar-lo a afegir.", "inconsistent_device": "Els protocols esperats no s'han trobat durant el descobriment. Normalment aix\u00f2 indica un problema amb el DNS multicast (Zeroconf). Prova d'afegir el dispositiu de nou.", "invalid_config": "La configuraci\u00f3 d'aquest dispositiu no est\u00e0 completa. Intenta'l tornar a afegir.", + "ipv6_not_supported": "IPv6 no est\u00e0 suportat.", "no_devices_found": "No s'han trobat dispositius a la xarxa", "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", "setup_failed": "No s'ha pogut configurar el dispositiu.", diff --git a/homeassistant/components/apple_tv/translations/de.json b/homeassistant/components/apple_tv/translations/de.json index ca6f69f5442..48149ce8394 100644 --- a/homeassistant/components/apple_tv/translations/de.json +++ b/homeassistant/components/apple_tv/translations/de.json @@ -9,6 +9,7 @@ "device_not_found": "Das Ger\u00e4t wurde bei der Erkennung nicht gefunden. Bitte versuche es erneut hinzuzuf\u00fcgen.", "inconsistent_device": "Die erwarteten Protokolle wurden bei der Erkennung nicht gefunden. Dies deutet normalerweise auf ein Problem mit Multicast-DNS (Zeroconf) hin. Bitte versuche das Ger\u00e4t erneut hinzuzuf\u00fcgen.", "invalid_config": "Die Konfiguration f\u00fcr dieses Ger\u00e4t ist unvollst\u00e4ndig. Bitte versuche, es erneut hinzuzuf\u00fcgen.", + "ipv6_not_supported": "IPv6 wird nicht unterst\u00fctzt.", "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "setup_failed": "Fehler beim Einrichten des Ger\u00e4ts.", diff --git a/homeassistant/components/apple_tv/translations/el.json b/homeassistant/components/apple_tv/translations/el.json index 11a61899b84..cbe36bf56fb 100644 --- a/homeassistant/components/apple_tv/translations/el.json +++ b/homeassistant/components/apple_tv/translations/el.json @@ -9,6 +9,7 @@ "device_not_found": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "inconsistent_device": "\u03a4\u03b1 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03b1 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7. \u0391\u03c5\u03c4\u03cc \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03c5\u03c0\u03bf\u03b4\u03b7\u03bb\u03ce\u03bd\u03b5\u03b9 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1 \u03bc\u03b5 \u03c4\u03bf multicast DNS (Zeroconf). \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae.", "invalid_config": "\u0397 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bb\u03bb\u03b9\u03c0\u03ae\u03c2. \u03a0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "ipv6_not_supported": "\u03a4\u03bf IPv6 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9.", "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", "setup_failed": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.", diff --git a/homeassistant/components/apple_tv/translations/en.json b/homeassistant/components/apple_tv/translations/en.json index f455d590d79..792eaf32c29 100644 --- a/homeassistant/components/apple_tv/translations/en.json +++ b/homeassistant/components/apple_tv/translations/en.json @@ -2,11 +2,13 @@ "config": { "abort": { "already_configured": "Device is already configured", + "already_configured_device": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "backoff": "Device does not accept pairing requests at this time (you might have entered an invalid PIN code too many times), try again later.", "device_did_not_pair": "No attempt to finish pairing process was made from the device.", "device_not_found": "Device was not found during discovery, please try adding it again.", "inconsistent_device": "Expected protocols were not found during discovery. This normally indicates a problem with multicast DNS (Zeroconf). Please try adding the device again.", + "invalid_config": "The configuration for this device is incomplete. Please try adding it again.", "ipv6_not_supported": "IPv6 is not supported.", "no_devices_found": "No devices found on the network", "reauth_successful": "Re-authentication was successful", @@ -17,6 +19,7 @@ "already_configured": "Device is already configured", "invalid_auth": "Invalid authentication", "no_devices_found": "No devices found on the network", + "no_usable_service": "A device was found but could not identify any way to establish a connection to it. If you keep seeing this message, try specifying its IP address or restarting your Apple TV.", "unknown": "Unexpected error" }, "flow_title": "{name} ({type})", @@ -70,5 +73,6 @@ "description": "Configure general device settings" } } - } + }, + "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/et.json b/homeassistant/components/apple_tv/translations/et.json index fa660662d8b..8180b95fe12 100644 --- a/homeassistant/components/apple_tv/translations/et.json +++ b/homeassistant/components/apple_tv/translations/et.json @@ -9,6 +9,7 @@ "device_not_found": "Seadet avastamise ajal ei leitud, proovi seda uuesti lisada.", "inconsistent_device": "Eeldatavaid protokolle avastamise ajal ei leitud. See n\u00e4itab tavaliselt probleemi multcast DNS-iga (Zeroconf). Proovi seade uuesti lisada.", "invalid_config": "Selle seadme s\u00e4tted on puudulikud. Proovi see uuesti lisada.", + "ipv6_not_supported": "IPv6 ei ole toetatud.", "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet", "reauth_successful": "Taastuvastamine \u00f5nnestus", "setup_failed": "Seadme h\u00e4\u00e4lestamine nurjus.", diff --git a/homeassistant/components/apple_tv/translations/fr.json b/homeassistant/components/apple_tv/translations/fr.json index 510d22c79a5..6d7deea6e5a 100644 --- a/homeassistant/components/apple_tv/translations/fr.json +++ b/homeassistant/components/apple_tv/translations/fr.json @@ -9,6 +9,7 @@ "device_not_found": "L'appareil n'a pas \u00e9t\u00e9 trouv\u00e9 lors de la d\u00e9couverte, veuillez r\u00e9essayer de l'ajouter.", "inconsistent_device": "Les protocoles attendus n'ont pas \u00e9t\u00e9 trouv\u00e9s lors de la d\u00e9couverte. Cela indique normalement un probl\u00e8me avec le DNS multicast (Zeroconf). Veuillez r\u00e9essayer d'ajouter l'appareil.", "invalid_config": "La configuration de cet appareil est incompl\u00e8te. Veuillez r\u00e9essayer de l'ajouter.", + "ipv6_not_supported": "IPv6 n'est pas pris en charge.", "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", "setup_failed": "\u00c9chec de la configuration de l'appareil.", diff --git a/homeassistant/components/apple_tv/translations/he.json b/homeassistant/components/apple_tv/translations/he.json index 03e7dc5d4fe..61ca863bd66 100644 --- a/homeassistant/components/apple_tv/translations/he.json +++ b/homeassistant/components/apple_tv/translations/he.json @@ -4,6 +4,7 @@ "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", "already_configured_device": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", "already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea", + "ipv6_not_supported": "IPv6 \u05d0\u05d9\u05e0\u05d5 \u05e0\u05ea\u05de\u05da.", "no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea", "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7", "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" diff --git a/homeassistant/components/apple_tv/translations/hu.json b/homeassistant/components/apple_tv/translations/hu.json index f76063c5eeb..73a30fbdd9a 100644 --- a/homeassistant/components/apple_tv/translations/hu.json +++ b/homeassistant/components/apple_tv/translations/hu.json @@ -3,12 +3,13 @@ "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "backoff": "Az eszk\u00f6z jelenleg nem fogadja el a p\u00e1ros\u00edt\u00e1si k\u00e9relmeket (lehet, hogy t\u00fal sokszor adott meg \u00e9rv\u00e9nytelen PIN-k\u00f3dot), pr\u00f3b\u00e1lkozzon \u00fajra k\u00e9s\u0151bb.", "device_did_not_pair": "A p\u00e1ros\u00edt\u00e1s folyamat\u00e1t az eszk\u00f6zr\u0151l nem pr\u00f3b\u00e1lt\u00e1k befejezni.", "device_not_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a felder\u00edt\u00e9s sor\u00e1n, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg \u00fajra hozz\u00e1adni.", "inconsistent_device": "Az elv\u00e1rt protokollok nem tal\u00e1lhat\u00f3k a felder\u00edt\u00e9s sor\u00e1n. Ez \u00e1ltal\u00e1ban a multicast DNS (Zeroconf) probl\u00e9m\u00e1j\u00e1t jelzi. K\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg \u00fajra hozz\u00e1adni az eszk\u00f6zt.", "invalid_config": "Az eszk\u00f6z konfigur\u00e1l\u00e1sa nem teljes. K\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg \u00fajra hozz\u00e1adni.", + "ipv6_not_supported": "Az IPv6 nem t\u00e1mogatott.", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", "setup_failed": "Az eszk\u00f6z be\u00e1ll\u00edt\u00e1sa sikertelen.", @@ -21,14 +22,14 @@ "no_usable_service": "Tal\u00e1ltunk egy eszk\u00f6zt, de nem tudtuk azonos\u00edtani, hogyan lehetne kapcsolatot l\u00e9tes\u00edteni vele. Ha tov\u00e1bbra is ezt az \u00fczenetet l\u00e1tja, pr\u00f3b\u00e1lja meg megadni az IP-c\u00edm\u00e9t, vagy ind\u00edtsa \u00fajra az Apple TV-t.", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, - "flow_title": "{name}", + "flow_title": "{name} ({type})", "step": { "confirm": { - "description": "Arra k\u00e9sz\u00fcl, hogy felvegye {name} nev\u0171 Apple TV-t a Home Assistant p\u00e9ld\u00e1ny\u00e1ba. \n\n ** A folyamat befejez\u00e9s\u00e9hez t\u00f6bb PIN-k\u00f3dot kell megadnia. ** \n\nFelh\u00edvjuk figyelm\u00e9t, hogy ezzel az integr\u00e1ci\u00f3val *nem* fogja tudni kikapcsolni az Apple TV-t. Csak a Home Assistant saj\u00e1t m\u00e9dialej\u00e1tsz\u00f3ja kapcsol ki!", + "description": "\u00d6n a `{name}`, `{type}` t\u00edpus\u00fa eszk\u00f6zt k\u00e9sz\u00fcl hozz\u00e1adni a Home Assistanthoz.\n\n**A folyamat befejez\u00e9s\u00e9hez t\u00f6bb PIN k\u00f3dot is meg kell adnia.**\n\nK\u00e9rj\u00fck, vegye figyelembe, hogy ezzel az integr\u00e1ci\u00f3val *nem* tudja kikapcsolni az Apple TV k\u00e9sz\u00fcl\u00e9k\u00e9t. Csak a Home Assistant m\u00e9dialej\u00e1tsz\u00f3ja fog kikapcsolni!", "title": "Apple TV sikeresen hozz\u00e1adva" }, "pair_no_pin": { - "description": "P\u00e1ros\u00edt\u00e1sra van sz\u00fcks\u00e9g a {protocol} szolg\u00e1ltat\u00e1shoz. A folytat\u00e1shoz k\u00e9rj\u00fck, \u00edrja be az Apple TV {pin}-t.", + "description": "P\u00e1ros\u00edt\u00e1sra van sz\u00fcks\u00e9g a {protocol} szolg\u00e1ltat\u00e1shoz. A folytat\u00e1shoz k\u00e9rj\u00fck, \u00edrja be k\u00e9sz\u00fcl\u00e9ken a PIN k\u00f3dot: {pin}.", "title": "P\u00e1ros\u00edt\u00e1s" }, "pair_with_pin": { @@ -47,7 +48,7 @@ "title": "A p\u00e1ros\u00edt\u00e1s nem lehets\u00e9ges" }, "reconfigure": { - "description": "Ez az Apple TV csatlakoz\u00e1si neh\u00e9zs\u00e9gekkel k\u00fczd, ez\u00e9rt \u00fajra kell konfigur\u00e1lni.", + "description": "Konfigur\u00e1lja \u00fajra ezt az eszk\u00f6zt a m\u0171k\u00f6d\u0151k\u00e9pess\u00e9g vissza\u00e1ll\u00edt\u00e1s\u00e1hoz.", "title": "Eszk\u00f6z \u00fajrakonfigur\u00e1l\u00e1sa" }, "service_problem": { @@ -58,7 +59,7 @@ "data": { "device_input": "Eszk\u00f6z" }, - "description": "El\u0151sz\u00f6r \u00edrja be a hozz\u00e1adni k\u00edv\u00e1nt Apple TV eszk\u00f6znev\u00e9t (pl. Konyha vagy H\u00e1l\u00f3szoba) vagy IP-c\u00edm\u00e9t. Ha valamilyen eszk\u00f6zt automatikusan tal\u00e1ltak a h\u00e1l\u00f3zat\u00e1n, az al\u00e1bb l\u00e1that\u00f3. \n\nHa nem l\u00e1tja eszk\u00f6z\u00e9t, vagy b\u00e1rmilyen probl\u00e9m\u00e1t tapasztal, pr\u00f3b\u00e1lja meg megadni az eszk\u00f6z IP-c\u00edm\u00e9t. \n\n {devices}", + "description": "Kezdje a hozz\u00e1adni k\u00edv\u00e1nt Apple TV eszk\u00f6znev\u00e9nek (pl. Konyha vagy H\u00e1l\u00f3szoba) vagy IP-c\u00edm\u00e9nek megad\u00e1s\u00e1val. \n\n Ha nem l\u00e1tja az eszk\u00f6zt, vagy b\u00e1rmilyen probl\u00e9m\u00e1t tapasztal, pr\u00f3b\u00e1lja meg megadni az eszk\u00f6z IP-c\u00edm\u00e9t.", "title": "\u00daj Apple TV be\u00e1ll\u00edt\u00e1sa" } } diff --git a/homeassistant/components/apple_tv/translations/id.json b/homeassistant/components/apple_tv/translations/id.json index 8a978eca737..7120d0671b8 100644 --- a/homeassistant/components/apple_tv/translations/id.json +++ b/homeassistant/components/apple_tv/translations/id.json @@ -9,6 +9,7 @@ "device_not_found": "Perangkat tidak ditemukan selama penemuan, coba tambahkan lagi.", "inconsistent_device": "Protokol yang diharapkan tidak ditemukan selama penemuan. Ini biasanya terjadi karena masalah dengan DNS multicast (Zeroconf). Coba tambahkan perangkat lagi.", "invalid_config": "Konfigurasi untuk perangkat ini tidak lengkap. Coba tambahkan lagi.", + "ipv6_not_supported": "IPv6 tidak didukung.", "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", "reauth_successful": "Autentikasi ulang berhasil", "setup_failed": "Gagal menyiapkan perangkat.", diff --git a/homeassistant/components/apple_tv/translations/it.json b/homeassistant/components/apple_tv/translations/it.json index 47bd8612265..b1e3a06440f 100644 --- a/homeassistant/components/apple_tv/translations/it.json +++ b/homeassistant/components/apple_tv/translations/it.json @@ -9,6 +9,7 @@ "device_not_found": "Il dispositivo non \u00e8 stato trovato durante il rilevamento, prova ad aggiungerlo di nuovo.", "inconsistent_device": "I protocolli previsti non sono stati trovati durante il rilevamento. Questo normalmente indica un problema con DNS multicast (Zeroconf). Prova ad aggiungere di nuovo il dispositivo.", "invalid_config": "La configurazione per questo dispositivo \u00e8 incompleta. Prova ad aggiungerlo di nuovo.", + "ipv6_not_supported": "IPv6 non \u00e8 supportato.", "no_devices_found": "Nessun dispositivo trovato sulla rete", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "setup_failed": "Impossibile configurare il dispositivo.", diff --git a/homeassistant/components/apple_tv/translations/ja.json b/homeassistant/components/apple_tv/translations/ja.json index c70dda18d01..9984006365e 100644 --- a/homeassistant/components/apple_tv/translations/ja.json +++ b/homeassistant/components/apple_tv/translations/ja.json @@ -9,6 +9,7 @@ "device_not_found": "\u691c\u51fa\u4e2d\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u8ffd\u52a0\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002", "inconsistent_device": "\u691c\u51fa\u4e2d\u306b\u671f\u5f85\u3057\u305f\u30d7\u30ed\u30c8\u30b3\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u3053\u308c\u306f\u901a\u5e38\u3001\u30de\u30eb\u30c1\u30ad\u30e3\u30b9\u30c8DNS(Zeroconf)\u306b\u554f\u984c\u304c\u3042\u308b\u3053\u3068\u3092\u793a\u3057\u3066\u3044\u307e\u3059\u3002\u30c7\u30d0\u30a4\u30b9\u3092\u3082\u3046\u4e00\u5ea6\u8ffd\u52a0\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002", "invalid_config": "\u3053\u306e\u30c7\u30d0\u30a4\u30b9\u306e\u8a2d\u5b9a\u306f\u4e0d\u5b8c\u5168\u3067\u3059\u3002\u3082\u3046\u4e00\u5ea6\u8ffd\u52a0\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002", + "ipv6_not_supported": "IPv6\u306b\u306f\u5bfe\u5fdc\u3057\u3066\u3044\u307e\u305b\u3093\u3002", "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f", "setup_failed": "\u30c7\u30d0\u30a4\u30b9\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002", diff --git a/homeassistant/components/apple_tv/translations/nl.json b/homeassistant/components/apple_tv/translations/nl.json index 7fdc20c7291..8aaf120403c 100644 --- a/homeassistant/components/apple_tv/translations/nl.json +++ b/homeassistant/components/apple_tv/translations/nl.json @@ -9,6 +9,7 @@ "device_not_found": "Apparaat werd niet gevonden tijdens het zoeken, probeer het opnieuw toe te voegen.", "inconsistent_device": "De verwachte protocollen zijn niet gevonden tijdens het zoeken. Dit wijst gewoonlijk op een probleem met multicast DNS (Zeroconf). Probeer het apparaat opnieuw toe te voegen.", "invalid_config": "De configuratie voor dit apparaat is onvolledig. Probeer het opnieuw toe te voegen.", + "ipv6_not_supported": "IPv6 wordt niet ondersteund.", "no_devices_found": "Geen apparaten gevonden op het netwerk", "reauth_successful": "Herauthenticatie was succesvol", "setup_failed": "Kan het apparaat niet instellen.", diff --git a/homeassistant/components/apple_tv/translations/no.json b/homeassistant/components/apple_tv/translations/no.json index b24c48a396e..e364633597e 100644 --- a/homeassistant/components/apple_tv/translations/no.json +++ b/homeassistant/components/apple_tv/translations/no.json @@ -9,6 +9,7 @@ "device_not_found": "Enheten ble ikke funnet under oppdagelsen. Pr\u00f8v \u00e5 legge den til p\u00e5 nytt.", "inconsistent_device": "Forventede protokoller ble ikke funnet under oppdagelsen. Dette indikerer vanligvis et problem med multicast DNS (Zeroconf). Pr\u00f8v \u00e5 legge til enheten p\u00e5 nytt.", "invalid_config": "Konfigurasjonen for denne enheten er ufullstendig. Pr\u00f8v \u00e5 legge den til p\u00e5 nytt.", + "ipv6_not_supported": "IPv6 st\u00f8ttes ikke.", "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", "setup_failed": "Kunne ikke konfigurere enheten.", diff --git a/homeassistant/components/apple_tv/translations/pl.json b/homeassistant/components/apple_tv/translations/pl.json index 1ba9b46dc3b..303de09c1dd 100644 --- a/homeassistant/components/apple_tv/translations/pl.json +++ b/homeassistant/components/apple_tv/translations/pl.json @@ -9,6 +9,7 @@ "device_not_found": "Urz\u0105dzenie nie zosta\u0142o znalezione podczas wykrywania, spr\u00f3buj doda\u0107 je ponownie.", "inconsistent_device": "Oczekiwane protoko\u0142y nie zosta\u0142y znalezione podczas wykrywania. Zwykle wskazuje to na problem z multicastem DNS (Zeroconf). Spr\u00f3buj ponownie doda\u0107 urz\u0105dzenie.", "invalid_config": "Konfiguracja tego urz\u0105dzenia jest niekompletna. Spr\u00f3buj doda\u0107 go ponownie.", + "ipv6_not_supported": "IPv6 nie jest obs\u0142ugiwany.", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", "setup_failed": "Nie uda\u0142o si\u0119 skonfigurowa\u0107 urz\u0105dzenia.", diff --git a/homeassistant/components/apple_tv/translations/pt-BR.json b/homeassistant/components/apple_tv/translations/pt-BR.json index 79fee5f02ec..539335c17d3 100644 --- a/homeassistant/components/apple_tv/translations/pt-BR.json +++ b/homeassistant/components/apple_tv/translations/pt-BR.json @@ -9,6 +9,7 @@ "device_not_found": "O dispositivo n\u00e3o foi encontrado durante a descoberta. Tente adicion\u00e1-lo novamente.", "inconsistent_device": "Os protocolos esperados n\u00e3o foram encontrados durante a descoberta. Isso normalmente indica um problema com o DNS multicast (Zeroconf). Tente adicionar o dispositivo novamente.", "invalid_config": "A configura\u00e7\u00e3o deste dispositivo est\u00e1 incompleta. Tente adicion\u00e1-lo novamente.", + "ipv6_not_supported": "IPv6 n\u00e3o \u00e9 suportado.", "no_devices_found": "Nenhum dispositivo encontrado na rede", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", "setup_failed": "Falha ao configurar o dispositivo.", diff --git a/homeassistant/components/apple_tv/translations/ru.json b/homeassistant/components/apple_tv/translations/ru.json index 4863a541603..88516d45af3 100644 --- a/homeassistant/components/apple_tv/translations/ru.json +++ b/homeassistant/components/apple_tv/translations/ru.json @@ -9,6 +9,7 @@ "device_not_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u0435\u0449\u0451 \u0440\u0430\u0437.", "inconsistent_device": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0435 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b. \u041e\u0431\u044b\u0447\u043d\u043e \u044d\u0442\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0441 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u044b\u043c DNS (Zeroconf). \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0435\u0449\u0451 \u0440\u0430\u0437.", "invalid_config": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u0435\u0449\u0451 \u0440\u0430\u0437.", + "ipv6_not_supported": "IPv6 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e.", "setup_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", diff --git a/homeassistant/components/apple_tv/translations/tr.json b/homeassistant/components/apple_tv/translations/tr.json index 4b9c2a4ca07..cee9fcce81e 100644 --- a/homeassistant/components/apple_tv/translations/tr.json +++ b/homeassistant/components/apple_tv/translations/tr.json @@ -9,6 +9,7 @@ "device_not_found": "Cihaz ke\u015fif s\u0131ras\u0131nda bulunamad\u0131, l\u00fctfen tekrar eklemeyi deneyin.", "inconsistent_device": "Ke\u015fif s\u0131ras\u0131nda beklenen protokoller bulunamad\u0131. Bu normalde \u00e7ok noktaya yay\u0131n DNS (Zeroconf) ile ilgili bir sorunu g\u00f6sterir. L\u00fctfen cihaz\u0131 tekrar eklemeyi deneyin.", "invalid_config": "Bu ayg\u0131t\u0131n yap\u0131land\u0131rmas\u0131 tamamlanmad\u0131. L\u00fctfen tekrar eklemeyi deneyin.", + "ipv6_not_supported": "IPv6 desteklenmiyor.", "no_devices_found": "A\u011fda cihaz bulunamad\u0131", "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", "setup_failed": "Cihaz kurulumu ba\u015far\u0131s\u0131z.", diff --git a/homeassistant/components/apple_tv/translations/zh-Hant.json b/homeassistant/components/apple_tv/translations/zh-Hant.json index 41732c0813e..b4e5108d474 100644 --- a/homeassistant/components/apple_tv/translations/zh-Hant.json +++ b/homeassistant/components/apple_tv/translations/zh-Hant.json @@ -9,6 +9,7 @@ "device_not_found": "\u641c\u5c0b\u4e0d\u5230\u88dd\u7f6e\u3001\u8acb\u8a66\u8457\u518d\u65b0\u589e\u4e00\u6b21\u3002", "inconsistent_device": "\u641c\u5c0b\u4e0d\u5230\u9810\u671f\u7684\u901a\u8a0a\u5354\u5b9a\u3002\u901a\u5e38\u539f\u56e0\u70ba Multicast DNS (Zeroconf) \u554f\u984c\u3001\u8acb\u8a66\u8457\u518d\u65b0\u589e\u4e00\u6b21\u3002", "invalid_config": "\u6b64\u88dd\u7f6e\u8a2d\u5b9a\u4e0d\u5b8c\u6574\uff0c\u8acb\u7a0d\u5019\u518d\u8a66\u4e00\u6b21\u3002", + "ipv6_not_supported": "\u4e0d\u652f\u63f4 IPv6\u3002", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", "setup_failed": "\u88dd\u7f6e\u8a2d\u5b9a\u5931\u6557\u3002", diff --git a/homeassistant/components/aquostv/media_player.py b/homeassistant/components/aquostv/media_player.py index c692b4017ae..ccbae7151b2 100644 --- a/homeassistant/components/aquostv/media_player.py +++ b/homeassistant/components/aquostv/media_player.py @@ -6,18 +6,10 @@ import logging import sharp_aquos_rc import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -43,18 +35,6 @@ DEFAULT_PASSWORD = "password" DEFAULT_TIMEOUT = 0.5 DEFAULT_RETRIES = 2 -SUPPORT_SHARPTV = ( - SUPPORT_TURN_OFF - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_SET - | SUPPORT_PLAY -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -122,7 +102,17 @@ class SharpAquosTVDevice(MediaPlayerEntity): """Representation of a Aquos TV.""" _attr_source_list = list(SOURCES.values()) - _attr_supported_features = SUPPORT_SHARPTV + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.PLAY + ) def __init__( self, name: str, remote: sharp_aquos_rc.TV, power_on_enabled: bool = False @@ -130,7 +120,7 @@ class SharpAquosTVDevice(MediaPlayerEntity): """Initialize the aquos device.""" self._power_on_enabled = power_on_enabled if power_on_enabled: - self._attr_supported_features |= SUPPORT_TURN_ON + self._attr_supported_features |= MediaPlayerEntityFeature.TURN_ON # Save a reference to the imported class self._attr_name = name # Assume that the TV is not muted diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index 583fa5f66c4..245e309af9c 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -4,20 +4,15 @@ import logging from arcam.fmj import SourceCodes from arcam.fmj.state import State -from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_CLASS_DIRECTORY, MEDIA_CLASS_MUSIC, MEDIA_TYPE_MUSIC, - SUPPORT_BROWSE_MEDIA, - SUPPORT_PLAY_MEDIA, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError from homeassistant.config_entries import ConfigEntry @@ -77,17 +72,17 @@ class ArcamFmj(MediaPlayerEntity): self._attr_name = f"{device_name} - Zone: {state.zn}" self._uuid = uuid self._attr_supported_features = ( - SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY_MEDIA - | SUPPORT_BROWSE_MEDIA - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_STEP - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON + MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON ) if state.zn == 1: - self._attr_supported_features |= SUPPORT_SELECT_SOUND_MODE + self._attr_supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE self._attr_unique_id = f"{uuid}-{state.zn}" self._attr_entity_registry_enabled_default = state.zn == 1 diff --git a/homeassistant/components/arcam_fmj/translations/hu.json b/homeassistant/components/arcam_fmj/translations/hu.json index 964ebe2a33d..897b462eb48 100644 --- a/homeassistant/components/arcam_fmj/translations/hu.json +++ b/homeassistant/components/arcam_fmj/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { diff --git a/homeassistant/components/arcam_fmj/translations/it.json b/homeassistant/components/arcam_fmj/translations/it.json index 2b99566888b..637bfa6533d 100644 --- a/homeassistant/components/arcam_fmj/translations/it.json +++ b/homeassistant/components/arcam_fmj/translations/it.json @@ -6,8 +6,8 @@ "cannot_connect": "Impossibile connettersi" }, "error": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "flow_title": "{host}", "step": { diff --git a/homeassistant/components/arest/manifest.json b/homeassistant/components/arest/manifest.json index 8a3b676c518..b59cea33d07 100644 --- a/homeassistant/components/arest/manifest.json +++ b/homeassistant/components/arest/manifest.json @@ -2,6 +2,6 @@ "domain": "arest", "name": "aREST", "documentation": "https://www.home-assistant.io/integrations/arest", - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "local_polling" } diff --git a/homeassistant/components/arlo/__init__.py b/homeassistant/components/arlo/__init__.py deleted file mode 100644 index f7a368c7a4c..00000000000 --- a/homeassistant/components/arlo/__init__.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Support for Netgear Arlo IP cameras.""" -from __future__ import annotations - -from datetime import datetime, timedelta -import logging - -from pyarlo import PyArlo -from requests.exceptions import ConnectTimeout, HTTPError -import voluptuous as vol - -from homeassistant.components import persistent_notification -from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME -from homeassistant.core import HomeAssistant, ServiceCall -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.event import track_time_interval -from homeassistant.helpers.typing import ConfigType - -_LOGGER = logging.getLogger(__name__) - -ATTRIBUTION = "Data provided by arlo.netgear.com" - -DATA_ARLO = "data_arlo" -DEFAULT_BRAND = "Netgear Arlo" -DOMAIN = "arlo" - -NOTIFICATION_ID = "arlo_notification" -NOTIFICATION_TITLE = "Arlo Component Setup" - -SCAN_INTERVAL = timedelta(seconds=60) - -SIGNAL_UPDATE_ARLO = "arlo_update" - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -def setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up an Arlo component.""" - conf = config[DOMAIN] - username = conf[CONF_USERNAME] - password = conf[CONF_PASSWORD] - scan_interval = conf[CONF_SCAN_INTERVAL] - - try: - - arlo = PyArlo(username, password, preload=False) - if not arlo.is_connected: - return False - - # assign refresh period to base station thread - arlo_base_station = next((station for station in arlo.base_stations), None) - - if arlo_base_station is not None: - arlo_base_station.refresh_rate = scan_interval.total_seconds() - elif not arlo.cameras: - _LOGGER.error("No Arlo camera or base station available") - return False - - hass.data[DATA_ARLO] = arlo - - except (ConnectTimeout, HTTPError) as ex: - _LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex)) - persistent_notification.create( - hass, - f"Error: {ex}
You will need to restart hass after fixing.", - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) - return False - - def hub_refresh(_: ServiceCall | datetime) -> None: - """Call ArloHub to refresh information.""" - _LOGGER.debug("Updating Arlo Hub component") - hass.data[DATA_ARLO].update(update_cameras=True, update_base_station=True) - dispatcher_send(hass, SIGNAL_UPDATE_ARLO) - - # register service - hass.services.register(DOMAIN, "update", hub_refresh) - - # register scan interval for ArloHub - track_time_interval(hass, hub_refresh, scan_interval) - return True diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py deleted file mode 100644 index 6026d284bf5..00000000000 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Support for Arlo Alarm Control Panels.""" -from __future__ import annotations - -import logging - -import voluptuous as vol - -from homeassistant.components.alarm_control_panel import ( - PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, - AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, -) -from homeassistant.const import ( - ATTR_ATTRIBUTION, - ATTR_DEVICE_ID, - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_DISARMED, -) -from homeassistant.core import HomeAssistant, callback -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import ATTRIBUTION, DATA_ARLO, SIGNAL_UPDATE_ARLO - -_LOGGER = logging.getLogger(__name__) - -ARMED = "armed" - -CONF_HOME_MODE_NAME = "home_mode_name" -CONF_AWAY_MODE_NAME = "away_mode_name" -CONF_NIGHT_MODE_NAME = "night_mode_name" - -ICON = "mdi:security" - -PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string, - vol.Optional(CONF_AWAY_MODE_NAME, default=ARMED): cv.string, - vol.Optional(CONF_NIGHT_MODE_NAME, default=ARMED): cv.string, - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the Arlo Alarm Control Panels.""" - arlo = hass.data[DATA_ARLO] - - if not arlo.base_stations: - return - - home_mode_name = config[CONF_HOME_MODE_NAME] - away_mode_name = config[CONF_AWAY_MODE_NAME] - night_mode_name = config[CONF_NIGHT_MODE_NAME] - base_stations = [] - for base_station in arlo.base_stations: - base_stations.append( - ArloBaseStation( - base_station, home_mode_name, away_mode_name, night_mode_name - ) - ) - add_entities(base_stations, True) - - -class ArloBaseStation(AlarmControlPanelEntity): - """Representation of an Arlo Alarm Control Panel.""" - - _attr_supported_features = ( - SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - ) - _attr_icon = ICON - - def __init__(self, data, home_mode_name, away_mode_name, night_mode_name): - """Initialize the alarm control panel.""" - self._base_station = data - self._home_mode_name = home_mode_name - self._away_mode_name = away_mode_name - self._night_mode_name = night_mode_name - self._attr_name = data.name - self._attr_extra_state_attributes = { - ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_DEVICE_ID: data.device_id, - } - - async def async_added_to_hass(self): - """Register callbacks.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self._update_callback - ) - ) - - @callback - def _update_callback(self): - """Call update method.""" - self.async_schedule_update_ha_state(True) - - def update(self): - """Update the state of the device.""" - _LOGGER.debug("Updating Arlo Alarm Control Panel %s", self.name) - mode = self._base_station.mode - self._attr_state = self._get_state_from_mode(mode) if mode else None - - def alarm_disarm(self, code=None): - """Send disarm command.""" - self._base_station.mode = STATE_ALARM_DISARMED - - def alarm_arm_away(self, code=None): - """Send arm away command. Uses custom mode.""" - self._base_station.mode = self._away_mode_name - - def alarm_arm_home(self, code=None): - """Send arm home command. Uses custom mode.""" - self._base_station.mode = self._home_mode_name - - def alarm_arm_night(self, code=None): - """Send arm night command. Uses custom mode.""" - self._base_station.mode = self._night_mode_name - - def _get_state_from_mode(self, mode): - """Convert Arlo mode to Home Assistant state.""" - if mode == ARMED: - return STATE_ALARM_ARMED_AWAY - if mode == STATE_ALARM_DISARMED: - return STATE_ALARM_DISARMED - if mode == self._home_mode_name: - return STATE_ALARM_ARMED_HOME - if mode == self._away_mode_name: - return STATE_ALARM_ARMED_AWAY - if mode == self._night_mode_name: - return STATE_ALARM_ARMED_NIGHT - return mode diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py deleted file mode 100644 index 4eecef7ba4e..00000000000 --- a/homeassistant/components/arlo/camera.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Support for Netgear Arlo IP cameras.""" -from __future__ import annotations - -import logging - -from haffmpeg.camera import CameraMjpeg -import voluptuous as vol - -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.components.ffmpeg import get_ffmpeg_manager -from homeassistant.const import ATTR_BATTERY_LEVEL -from homeassistant.core import HomeAssistant -from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO - -_LOGGER = logging.getLogger(__name__) - -ARLO_MODE_ARMED = "armed" -ARLO_MODE_DISARMED = "disarmed" - -ATTR_BRIGHTNESS = "brightness" -ATTR_FLIPPED = "flipped" -ATTR_MIRRORED = "mirrored" -ATTR_MOTION = "motion_detection_sensitivity" -ATTR_POWERSAVE = "power_save_mode" -ATTR_SIGNAL_STRENGTH = "signal_strength" -ATTR_UNSEEN_VIDEOS = "unseen_videos" -ATTR_LAST_REFRESH = "last_refresh" - -CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -DEFAULT_ARGUMENTS = "-pred 1" - -POWERSAVE_MODE_MAPPING = {1: "best_battery_life", 2: "optimized", 3: "best_video"} - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string} -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up an Arlo IP Camera.""" - arlo = hass.data[DATA_ARLO] - - cameras = [] - for camera in arlo.cameras: - cameras.append(ArloCam(hass, camera, config)) - - add_entities(cameras) - - -class ArloCam(Camera): - """An implementation of a Netgear Arlo IP camera.""" - - def __init__(self, hass, camera, device_info): - """Initialize an Arlo camera.""" - super().__init__() - self._camera = camera - self._attr_name = camera.name - self._motion_status = False - self._ffmpeg = get_ffmpeg_manager(hass) - self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS) - self._last_refresh = None - self.attrs = {} - - def camera_image( - self, width: int | None = None, height: int | None = None - ) -> bytes | None: - """Return a still image response from the camera.""" - return self._camera.last_image_from_cache - - async def async_added_to_hass(self): - """Register callbacks.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self.async_write_ha_state - ) - ) - - async def handle_async_mjpeg_stream(self, request): - """Generate an HTTP MJPEG stream from the camera.""" - video = await self.hass.async_add_executor_job( - getattr, self._camera, "last_video" - ) - - if not video: - error_msg = ( - f"Video not found for {self.name}. " - f"Is it older than {self._camera.min_days_vdo_cache} days?" - ) - _LOGGER.error(error_msg) - return - - stream = CameraMjpeg(self._ffmpeg.binary) - await stream.open_camera(video.video_url, extra_cmd=self._ffmpeg_arguments) - - try: - stream_reader = await stream.get_reader() - return await async_aiohttp_proxy_stream( - self.hass, - request, - stream_reader, - self._ffmpeg.ffmpeg_stream_content_type, - ) - finally: - await stream.close() - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return { - name: value - for name, value in ( - (ATTR_BATTERY_LEVEL, self._camera.battery_level), - (ATTR_BRIGHTNESS, self._camera.brightness), - (ATTR_FLIPPED, self._camera.flip_state), - (ATTR_MIRRORED, self._camera.mirror_state), - (ATTR_MOTION, self._camera.motion_detection_sensitivity), - ( - ATTR_POWERSAVE, - POWERSAVE_MODE_MAPPING.get(self._camera.powersave_mode), - ), - (ATTR_SIGNAL_STRENGTH, self._camera.signal_strength), - (ATTR_UNSEEN_VIDEOS, self._camera.unseen_videos), - ) - if value is not None - } - - @property - def model(self): - """Return the camera model.""" - return self._camera.model_id - - @property - def brand(self): - """Return the camera brand.""" - return DEFAULT_BRAND - - @property - def motion_detection_enabled(self): - """Return the camera motion detection status.""" - return self._motion_status - - def set_base_station_mode(self, mode): - """Set the mode in the base station.""" - # Get the list of base stations identified by library - - # Some Arlo cameras does not have base station - # So check if there is base station detected first - # if yes, then choose the primary base station - # Set the mode on the chosen base station - if base_stations := self.hass.data[DATA_ARLO].base_stations: - primary_base_station = base_stations[0] - primary_base_station.mode = mode - - def enable_motion_detection(self): - """Enable the Motion detection in base station (Arm).""" - self._motion_status = True - self.set_base_station_mode(ARLO_MODE_ARMED) - - def disable_motion_detection(self): - """Disable the motion detection in base station (Disarm).""" - self._motion_status = False - self.set_base_station_mode(ARLO_MODE_DISARMED) diff --git a/homeassistant/components/arlo/manifest.json b/homeassistant/components/arlo/manifest.json deleted file mode 100644 index 5ba5180b914..00000000000 --- a/homeassistant/components/arlo/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "domain": "arlo", - "name": "Arlo", - "documentation": "https://www.home-assistant.io/integrations/arlo", - "requirements": ["pyarlo==0.2.4"], - "dependencies": ["ffmpeg"], - "codeowners": [], - "iot_class": "cloud_polling", - "loggers": ["pyarlo", "sseclient_py"] -} diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py deleted file mode 100644 index ad0aee77b5a..00000000000 --- a/homeassistant/components/arlo/sensor.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Sensor support for Netgear Arlo IP cameras.""" -from __future__ import annotations - -from dataclasses import replace -import logging - -import voluptuous as vol - -from homeassistant.components.sensor import ( - PLATFORM_SCHEMA, - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, -) -from homeassistant.const import ( - CONCENTRATION_PARTS_PER_MILLION, - CONF_MONITORED_CONDITIONS, - PERCENTAGE, - TEMP_CELSIUS, -) -from homeassistant.core import HomeAssistant, callback -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.icon import icon_for_battery_level -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from . import ATTRIBUTION, DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO - -_LOGGER = logging.getLogger(__name__) - -SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="last_capture", - name="Last", - icon="mdi:run-fast", - ), - SensorEntityDescription( - key="total_cameras", - name="Arlo Cameras", - icon="mdi:video", - ), - SensorEntityDescription( - key="captured_today", - name="Captured Today", - icon="mdi:file-video", - ), - SensorEntityDescription( - key="battery_level", - name="Battery Level", - native_unit_of_measurement=PERCENTAGE, - device_class=SensorDeviceClass.BATTERY, - ), - SensorEntityDescription( - key="signal_strength", - name="Signal Strength", - icon="mdi:signal", - ), - SensorEntityDescription( - key="temperature", - name="Temperature", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - ), - SensorEntityDescription( - key="humidity", - name="Humidity", - native_unit_of_measurement=PERCENTAGE, - device_class=SensorDeviceClass.HUMIDITY, - ), - SensorEntityDescription( - key="air_quality", - name="Air Quality", - native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, - icon="mdi:biohazard", - ), -) - -SENSOR_KEYS = [desc.key for desc in SENSOR_TYPES] - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All( - cv.ensure_list, [vol.In(SENSOR_KEYS)] - ) - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up an Arlo IP sensor.""" - if not (arlo := hass.data.get(DATA_ARLO)): - return - - sensors = [] - for sensor_original in SENSOR_TYPES: - if sensor_original.key not in config[CONF_MONITORED_CONDITIONS]: - continue - sensor_entry = replace(sensor_original) - if sensor_entry.key == "total_cameras": - sensors.append(ArloSensor(arlo, sensor_entry)) - else: - for camera in arlo.cameras: - if sensor_entry.key in ("temperature", "humidity", "air_quality"): - continue - - sensor_entry.name = f"{sensor_entry.name} {camera.name}" - sensors.append(ArloSensor(camera, sensor_entry)) - - for base_station in arlo.base_stations: - if ( - sensor_entry.key in ("temperature", "humidity", "air_quality") - and base_station.model_id == "ABC1000" - ): - sensor_entry.name = f"{sensor_entry.name} {base_station.name}" - sensors.append(ArloSensor(base_station, sensor_entry)) - - add_entities(sensors, True) - - -class ArloSensor(SensorEntity): - """An implementation of a Netgear Arlo IP sensor.""" - - _attr_attribution = ATTRIBUTION - - def __init__(self, device, sensor_entry): - """Initialize an Arlo sensor.""" - self.entity_description = sensor_entry - self._data = device - self._state = None - - async def async_added_to_hass(self): - """Register callbacks.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self._update_callback - ) - ) - - @callback - def _update_callback(self): - """Call update method.""" - self.async_schedule_update_ha_state(True) - - @property - def native_value(self): - """Return the state of the sensor.""" - return self._state - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - if self.entity_description.key == "battery_level" and self._state is not None: - return icon_for_battery_level( - battery_level=int(self._state), charging=False - ) - return self.entity_description.icon - - def update(self): - """Get the latest data and updates the state.""" - _LOGGER.debug("Updating Arlo sensor %s", self.name) - if self.entity_description.key == "total_cameras": - self._state = len(self._data.cameras) - - elif self.entity_description.key == "captured_today": - self._state = len(self._data.captured_today) - - elif self.entity_description.key == "last_capture": - try: - video = self._data.last_video - self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S") - except (AttributeError, IndexError): - error_msg = ( - f"Video not found for {self.name}. " - f"Older than {self._data.min_days_vdo_cache} days?" - ) - _LOGGER.debug(error_msg) - self._state = None - - elif self.entity_description.key == "battery_level": - try: - self._state = self._data.battery_level - except TypeError: - self._state = None - - elif self.entity_description.key == "signal_strength": - try: - self._state = self._data.signal_strength - except TypeError: - self._state = None - - elif self.entity_description.key == "temperature": - try: - self._state = self._data.ambient_temperature - except TypeError: - self._state = None - - elif self.entity_description.key == "humidity": - try: - self._state = self._data.ambient_humidity - except TypeError: - self._state = None - - elif self.entity_description.key == "air_quality": - try: - self._state = self._data.ambient_air_quality - except TypeError: - self._state = None - - @property - def extra_state_attributes(self): - """Return the device state attributes.""" - attrs = {} - - attrs["brand"] = DEFAULT_BRAND - - if self.entity_description.key != "total_cameras": - attrs["model"] = self._data.model_id - - return attrs diff --git a/homeassistant/components/arlo/services.yaml b/homeassistant/components/arlo/services.yaml deleted file mode 100644 index 8481ffc4d53..00000000000 --- a/homeassistant/components/arlo/services.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Describes the format for available arlo services - -update: - name: Update - description: Update the state for all cameras and the base station. diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index f735e520bdc..0532dd9c7cc 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -2,7 +2,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from .const import DATA_ASUSWRT, DOMAIN from .router import AsusWrtRouter @@ -18,7 +18,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: router.async_on_close(entry.add_update_listener(update_listener)) - async def async_close_connection(event): + async def async_close_connection(event: Event) -> None: """Close AsusWrt connection on HA Stop.""" await router.close() diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index a06071a33d1..ab62b879f75 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -1,15 +1,19 @@ """Config flow to configure the AsusWrt integration.""" + +from __future__ import annotations + import logging import os import socket +from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.device_tracker.const import ( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, ) +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_HOST, CONF_MODE, @@ -19,6 +23,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import ( @@ -29,7 +34,6 @@ from .const import ( CONF_TRACK_UNKNOWN, DEFAULT_DNSMASQ, DEFAULT_INTERFACE, - DEFAULT_SSH_PORT, DEFAULT_TRACK_UNKNOWN, DOMAIN, MODE_AP, @@ -46,13 +50,13 @@ RESULT_SUCCESS = "success" _LOGGER = logging.getLogger(__name__) -def _is_file(value) -> bool: +def _is_file(value: str) -> bool: """Validate that the value is an existing file.""" - file_in = os.path.expanduser(str(value)) + file_in = os.path.expanduser(value) return os.path.isfile(file_in) and os.access(file_in, os.R_OK) -def _get_ip(host): +def _get_ip(host: str) -> str | None: """Get the ip address from the host name.""" try: return socket.gethostbyname(host) @@ -60,63 +64,69 @@ def _get_ip(host): return None -class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 - def __init__(self): - """Initialize AsusWrt config flow.""" - self._host = None - @callback - def _show_setup_form(self, user_input=None, errors=None): + def _show_setup_form( + self, + user_input: dict[str, Any] | None = None, + errors: dict[str, str] | None = None, + ) -> FlowResult: """Show the setup form to the user.""" if user_input is None: user_input = {} + adv_schema = {} + conf_password = vol.Required(CONF_PASSWORD) + if self.show_advanced_options: + conf_password = vol.Optional(CONF_PASSWORD) + adv_schema[vol.Optional(CONF_PORT)] = cv.port + adv_schema[vol.Optional(CONF_SSH_KEY)] = str + + schema = { + vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, "")): str, + vol.Required(CONF_USERNAME, default=user_input.get(CONF_USERNAME, "")): str, + conf_password: str, + vol.Required(CONF_PROTOCOL, default=PROTOCOL_SSH): vol.In( + {PROTOCOL_SSH: "SSH", PROTOCOL_TELNET: "Telnet"} + ), + **adv_schema, + vol.Required(CONF_MODE, default=MODE_ROUTER): vol.In( + {MODE_ROUTER: "Router", MODE_AP: "Access Point"} + ), + } + return self.async_show_form( step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, "")): str, - vol.Required( - CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") - ): str, - vol.Optional(CONF_PASSWORD): str, - vol.Optional(CONF_SSH_KEY): str, - vol.Required(CONF_PROTOCOL, default=PROTOCOL_SSH): vol.In( - {PROTOCOL_SSH: "SSH", PROTOCOL_TELNET: "Telnet"} - ), - vol.Required(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, - vol.Required(CONF_MODE, default=MODE_ROUTER): vol.In( - {MODE_ROUTER: "Router", MODE_AP: "Access Point"} - ), - } - ), + data_schema=vol.Schema(schema), errors=errors or {}, ) - async def _async_check_connection(self, user_input): + @staticmethod + async def _async_check_connection(user_input: dict[str, Any]) -> str: """Attempt to connect the AsusWrt router.""" + host: str = user_input[CONF_HOST] api = get_api(user_input) try: await api.connection.async_connect() except OSError: - _LOGGER.error("Error connecting to the AsusWrt router at %s", self._host) + _LOGGER.error("Error connecting to the AsusWrt router at %s", host) return RESULT_CONN_ERROR except Exception: # pylint: disable=broad-except _LOGGER.exception( - "Unknown error connecting with AsusWrt router at %s", self._host + "Unknown error connecting with AsusWrt router at %s", host ) return RESULT_UNKNOWN if not api.is_connected: - _LOGGER.error("Error connecting to the AsusWrt router at %s", self._host) + _LOGGER.error("Error connecting to the AsusWrt router at %s", host) return RESULT_CONN_ERROR conf_protocol = user_input[CONF_PROTOCOL] @@ -124,7 +134,9 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): api.connection.disconnect() return RESULT_SUCCESS - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -132,10 +144,11 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is None: return self._show_setup_form(user_input) - errors = {} - self._host = user_input[CONF_HOST] - pwd = user_input.get(CONF_PASSWORD) - ssh = user_input.get(CONF_SSH_KEY) + errors: dict[str, str] = {} + host: str = user_input[CONF_HOST] + + pwd: str | None = user_input.get(CONF_PASSWORD) + ssh: str | None = user_input.get(CONF_SSH_KEY) if not (pwd or ssh): errors["base"] = "pwd_or_ssh" @@ -148,7 +161,7 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "ssh_not_file" if not errors: - ip_address = await self.hass.async_add_executor_job(_get_ip, self._host) + ip_address = await self.hass.async_add_executor_job(_get_ip, host) if not ip_address: errors["base"] = "invalid_host" @@ -161,25 +174,27 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self._show_setup_form(user_input, errors) return self.async_create_entry( - title=self._host, + title=host, data=user_input, ) @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for AsusWrt.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None): + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/asuswrt/const.py b/homeassistant/components/asuswrt/const.py index 95e93e0ff25..f80643f078d 100644 --- a/homeassistant/components/asuswrt/const.py +++ b/homeassistant/components/asuswrt/const.py @@ -11,7 +11,6 @@ DATA_ASUSWRT = DOMAIN DEFAULT_DNSMASQ = "/var/lib/misc" DEFAULT_INTERFACE = "eth0" -DEFAULT_SSH_PORT = 22 DEFAULT_TRACK_UNKNOWN = False MODE_AP = "ap" diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 60aa98079c2..af43294c954 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -9,7 +9,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DATA_ASUSWRT, DOMAIN -from .router import AsusWrtRouter +from .router import AsusWrtDevInfo, AsusWrtRouter DEFAULT_DEVICE_NAME = "Unknown device" @@ -22,7 +22,7 @@ async def async_setup_entry( tracked: set = set() @callback - def update_router(): + def update_router() -> None: """Update the values of the router.""" add_entities(router, async_add_entities, tracked) @@ -34,7 +34,9 @@ async def async_setup_entry( @callback -def add_entities(router, async_add_entities, tracked): +def add_entities( + router: AsusWrtRouter, async_add_entities: AddEntitiesCallback, tracked: set[str] +) -> None: """Add new tracker entities from the router.""" new_tracked = [] @@ -54,7 +56,7 @@ class AsusWrtDevice(ScannerEntity): _attr_should_poll = False - def __init__(self, router: AsusWrtRouter, device) -> None: + def __init__(self, router: AsusWrtRouter, device: AsusWrtDevInfo) -> None: """Initialize a AsusWrt device.""" self._router = router self._device = device @@ -62,7 +64,7 @@ class AsusWrtDevice(ScannerEntity): self._attr_name = device.name or DEFAULT_DEVICE_NAME @property - def is_connected(self): + def is_connected(self) -> bool: """Return true if the device is connected to the network.""" return self._device.is_connected @@ -72,7 +74,7 @@ class AsusWrtDevice(ScannerEntity): return SOURCE_TYPE_ROUTER @property - def hostname(self) -> str: + def hostname(self) -> str | None: """Return the hostname of device.""" return self._device.name @@ -82,7 +84,7 @@ class AsusWrtDevice(ScannerEntity): return "mdi:lan-connect" if self._device.is_connected else "mdi:lan-disconnect" @property - def ip_address(self) -> str: + def ip_address(self) -> str | None: """Return the primary ip address of the device.""" return self._device.ip_address @@ -92,7 +94,7 @@ class AsusWrtDevice(ScannerEntity): return self._device.mac @callback - def async_on_demand_update(self): + def async_on_demand_update(self) -> None: """Update state.""" self._device = self._router.devices[self._device.mac] self._attr_extra_state_attributes = {} @@ -102,7 +104,7 @@ class AsusWrtDevice(ScannerEntity): ] = self._device.last_activity.isoformat(timespec="seconds") self.async_write_ha_state() - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Register state update callback.""" self.async_on_remove( async_dispatcher_connect( diff --git a/homeassistant/components/asuswrt/diagnostics.py b/homeassistant/components/asuswrt/diagnostics.py index dc26bca5512..16f5468f87d 100644 --- a/homeassistant/components/asuswrt/diagnostics.py +++ b/homeassistant/components/asuswrt/diagnostics.py @@ -3,9 +3,16 @@ from __future__ import annotations from typing import Any +import attr + from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + ATTR_CONNECTIONS, + ATTR_IDENTIFIERS, + CONF_PASSWORD, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -13,6 +20,7 @@ from .const import DATA_ASUSWRT, DOMAIN from .router import AsusWrtRouter TO_REDACT = {CONF_PASSWORD, CONF_USERNAME} +TO_REDACT_DEV = {ATTR_CONNECTIONS, ATTR_IDENTIFIERS} async def async_get_config_entry_diagnostics( @@ -33,11 +41,7 @@ async def async_get_config_entry_diagnostics( return data data["device"] = { - "name": hass_device.name, - "name_by_user": hass_device.name_by_user, - "disabled": hass_device.disabled, - "disabled_by": hass_device.disabled_by, - "device_info": async_redact_data(dict(router.device_info), {"identifiers"}), + **async_redact_data(attr.asdict(hass_device), TO_REDACT_DEV), "entities": {}, "tracked_devices": [], } @@ -59,16 +63,12 @@ async def async_get_config_entry_diagnostics( state_dict.pop("context", None) data["device"]["entities"][entity_entry.entity_id] = { - "name": entity_entry.name, - "original_name": entity_entry.original_name, - "disabled": entity_entry.disabled, - "disabled_by": entity_entry.disabled_by, - "entity_category": entity_entry.entity_category, - "device_class": entity_entry.device_class, - "original_device_class": entity_entry.original_device_class, - "icon": entity_entry.icon, - "original_icon": entity_entry.original_icon, - "unit_of_measurement": entity_entry.unit_of_measurement, + **async_redact_data( + attr.asdict( + entity_entry, filter=lambda attr, value: attr.name != "entity_id" + ), + TO_REDACT, + ), "state": state_dict, } diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 67e592c71ef..2484b9880c3 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta import logging from typing import Any -from aioasuswrt.asuswrt import AsusWrt +from aioasuswrt.asuswrt import AsusWrt, Device as WrtDevice from homeassistant.components.device_tracker.const import ( CONF_CONSIDER_HOME, @@ -79,17 +79,17 @@ def _get_dict(keys: list, values: list) -> dict[str, Any]: class AsusWrtSensorDataHandler: """Data handler for AsusWrt sensor.""" - def __init__(self, hass, api): + def __init__(self, hass: HomeAssistant, api: AsusWrt) -> None: """Initialize a AsusWrt sensor data handler.""" self._hass = hass self._api = api self._connected_devices = 0 - async def _get_connected_devices(self): + async def _get_connected_devices(self) -> dict[str, int]: """Return number of connected devices.""" return {SENSORS_CONNECTED_DEVICE[0]: self._connected_devices} - async def _get_bytes(self): + async def _get_bytes(self) -> dict[str, Any]: """Fetch byte information from the router.""" try: datas = await self._api.async_get_bytes_total() @@ -98,7 +98,7 @@ class AsusWrtSensorDataHandler: return _get_dict(SENSORS_BYTES, datas) - async def _get_rates(self): + async def _get_rates(self) -> dict[str, Any]: """Fetch rates information from the router.""" try: rates = await self._api.async_get_current_transfer_rates() @@ -107,7 +107,7 @@ class AsusWrtSensorDataHandler: return _get_dict(SENSORS_RATES, rates) - async def _get_load_avg(self): + async def _get_load_avg(self) -> dict[str, Any]: """Fetch load average information from the router.""" try: avg = await self._api.async_get_loadavg() @@ -116,23 +116,25 @@ class AsusWrtSensorDataHandler: return _get_dict(SENSORS_LOAD_AVG, avg) - async def _get_temperatures(self): + async def _get_temperatures(self) -> dict[str, Any]: """Fetch temperatures information from the router.""" try: - temperatures = await self._api.async_get_temperature() + temperatures: dict[str, Any] = await self._api.async_get_temperature() except (OSError, ValueError) as exc: raise UpdateFailed(exc) from exc return temperatures - def update_device_count(self, conn_devices: int): + def update_device_count(self, conn_devices: int) -> bool: """Update connected devices attribute.""" if self._connected_devices == conn_devices: return False self._connected_devices = conn_devices return True - async def get_coordinator(self, sensor_type: str, should_poll=True): + async def get_coordinator( + self, sensor_type: str, should_poll: bool = True + ) -> DataUpdateCoordinator: """Get the coordinator for a specific sensor type.""" if sensor_type == SENSORS_TYPE_COUNT: method = self._get_connected_devices @@ -163,15 +165,15 @@ class AsusWrtSensorDataHandler: class AsusWrtDevInfo: """Representation of a AsusWrt device info.""" - def __init__(self, mac, name=None): + def __init__(self, mac: str, name: str | None = None) -> None: """Initialize a AsusWrt device info.""" self._mac = mac self._name = name - self._ip_address = None - self._last_activity = None + self._ip_address: str | None = None + self._last_activity: datetime | None = None self._connected = False - def update(self, dev_info=None, consider_home=0): + def update(self, dev_info: WrtDevice | None = None, consider_home: int = 0) -> None: """Update AsusWrt device info.""" utc_point_in_time = dt_util.utcnow() if dev_info: @@ -183,32 +185,34 @@ class AsusWrtDevInfo: elif self._connected: self._connected = ( - utc_point_in_time - self._last_activity - ).total_seconds() < consider_home + self._last_activity is not None + and (utc_point_in_time - self._last_activity).total_seconds() + < consider_home + ) self._ip_address = None @property - def is_connected(self): + def is_connected(self) -> bool: """Return connected status.""" return self._connected @property - def mac(self): + def mac(self) -> str: """Return device mac address.""" return self._mac @property - def name(self): + def name(self) -> str | None: """Return device name.""" return self._name @property - def ip_address(self): + def ip_address(self) -> str | None: """Return device ip address.""" return self._ip_address @property - def last_activity(self): + def last_activity(self) -> datetime | None: """Return device last activity.""" return self._last_activity @@ -222,21 +226,21 @@ class AsusWrtRouter: self._entry = entry self._api: AsusWrt = None - self._protocol = entry.data[CONF_PROTOCOL] - self._host = entry.data[CONF_HOST] - self._model = "Asus Router" + self._protocol: str = entry.data[CONF_PROTOCOL] + self._host: str = entry.data[CONF_HOST] + self._model: str = "Asus Router" self._sw_v: str | None = None - self._devices: dict[str, Any] = {} - self._connected_devices = 0 - self._connect_error = False + self._devices: dict[str, AsusWrtDevInfo] = {} + self._connected_devices: int = 0 + self._connect_error: bool = False self._sensors_data_handler: AsusWrtSensorDataHandler | None = None self._sensors_coordinator: dict[str, Any] = {} self._on_close: list[Callable] = [] - self._options = { + self._options: dict[str, Any] = { CONF_DNSMASQ: DEFAULT_DNSMASQ, CONF_INTERFACE: DEFAULT_INTERFACE, CONF_REQUIRE_IP: True, @@ -277,7 +281,7 @@ class AsusWrtRouter: # migrate entity unique ID if wrong formatted if device_mac != entry.unique_id: existing_entity_id = entity_reg.async_get_entity_id( - DOMAIN, TRACKER_DOMAIN, device_mac + TRACKER_DOMAIN, DOMAIN, device_mac ) if existing_entity_id: # entity with uniqueid properly formatted already @@ -326,10 +330,12 @@ class AsusWrtRouter: _LOGGER.info("Reconnected to ASUS router %s", self._host) self._connected_devices = len(api_devices) - consider_home = self._options.get( + consider_home: int = self._options.get( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds() ) - track_unknown = self._options.get(CONF_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN) + track_unknown: bool = self._options.get( + CONF_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN + ) wrt_devices = {format_mac(mac): dev for mac, dev in api_devices.items()} for device_mac, device in self._devices.items(): @@ -357,15 +363,13 @@ class AsusWrtRouter: self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api) self._sensors_data_handler.update_device_count(self._connected_devices) - sensors_types = { + sensors_types: dict[str, list[str]] = { SENSORS_TYPE_BYTES: SENSORS_BYTES, SENSORS_TYPE_COUNT: SENSORS_CONNECTED_DEVICE, SENSORS_TYPE_LOAD_AVG: SENSORS_LOAD_AVG, SENSORS_TYPE_RATES: SENSORS_RATES, + SENSORS_TYPE_TEMPERATURES: await self._get_available_temperature_sensors(), } - sensors_types[ - SENSORS_TYPE_TEMPERATURES - ] = await self._get_available_temperature_sensors() for sensor_type, sensor_names in sensors_types.items(): if not sensor_names: @@ -388,7 +392,7 @@ class AsusWrtRouter: if self._sensors_data_handler.update_device_count(self._connected_devices): await coordinator.async_refresh() - async def _get_available_temperature_sensors(self): + async def _get_available_temperature_sensors(self) -> list[str]: """Check which temperature information is available on the router.""" try: availability = await self._api.async_find_temperature_commands() @@ -420,13 +424,13 @@ class AsusWrtRouter: """Add a function to call when router is closed.""" self._on_close.append(func) - def update_options(self, new_options: dict) -> bool: + def update_options(self, new_options: dict[str, Any]) -> bool: """Update router options.""" req_reload = False for name, new_opt in new_options.items(): if name in CONF_REQ_RELOAD: old_opt = self._options.get(name) - if not old_opt or old_opt != new_opt: + if old_opt is None or old_opt != new_opt: req_reload = True break @@ -461,7 +465,7 @@ class AsusWrtRouter: return self._host @property - def devices(self) -> dict[str, Any]: + def devices(self) -> dict[str, AsusWrtDevInfo]: """Return devices.""" return self._devices @@ -470,11 +474,6 @@ class AsusWrtRouter: """Return sensors coordinators.""" return self._sensors_coordinator - @property - def api(self) -> AsusWrt: - """Return router API.""" - return self._api - async def _get_nvram_info(api: AsusWrt, info_type: str) -> dict[str, Any]: """Get AsusWrt router info from nvram.""" @@ -487,13 +486,13 @@ async def _get_nvram_info(api: AsusWrt, info_type: str) -> dict[str, Any]: return info -def get_api(conf: dict, options: dict | None = None) -> AsusWrt: +def get_api(conf: dict[str, Any], options: dict[str, Any] | None = None) -> AsusWrt: """Get the AsusWrt API.""" opt = options or {} return AsusWrt( conf[CONF_HOST], - conf[CONF_PORT], + conf.get(CONF_PORT), conf[CONF_PROTOCOL] == PROTOCOL_TELNET, conf[CONF_USERNAME], conf.get(CONF_PASSWORD, ""), diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index bc04dbb28a3..5c46b3e693c 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -2,7 +2,6 @@ from __future__ import annotations from dataclasses import dataclass -from numbers import Real from homeassistant.components.sensor import ( SensorDeviceClass, @@ -197,10 +196,10 @@ class AsusWrtSensor(CoordinatorEntity, SensorEntity): self._attr_extra_state_attributes = {"hostname": router.host} @property - def native_value(self) -> float | str | None: + def native_value(self) -> float | int | str | None: """Return current state.""" descr = self.entity_description - state = self.coordinator.data.get(descr.key) - if state is not None and descr.factor and isinstance(state, Real): + state: float | int | str | None = self.coordinator.data.get(descr.key) + if state is not None and descr.factor and isinstance(state, (float, int)): return round(state / descr.factor, descr.precision) return state diff --git a/homeassistant/components/asuswrt/strings.json b/homeassistant/components/asuswrt/strings.json index ae94870eed3..56f3c659c0b 100644 --- a/homeassistant/components/asuswrt/strings.json +++ b/homeassistant/components/asuswrt/strings.json @@ -11,7 +11,7 @@ "password": "[%key:common::config_flow::data::password%]", "ssh_key": "Path to your SSH key file (instead of password)", "protocol": "Communication protocol to use", - "port": "[%key:common::config_flow::data::port%]", + "port": "[%key:common::config_flow::data::port%] (leave empty for protocol default)", "mode": "[%key:common::config_flow::data::mode%]" } } diff --git a/homeassistant/components/asuswrt/translations/ca.json b/homeassistant/components/asuswrt/translations/ca.json index d034aefb407..9149cd1ae7a 100644 --- a/homeassistant/components/asuswrt/translations/ca.json +++ b/homeassistant/components/asuswrt/translations/ca.json @@ -18,7 +18,7 @@ "mode": "Mode", "name": "Nom", "password": "Contrasenya", - "port": "Port", + "port": "Port (deixa-ho buit pel predeterminat del protocol)", "protocol": "Protocol de comunicacions a utilitzar", "ssh_key": "Ruta al fitxer de claus SSH (en lloc de la contrasenya)", "username": "Nom d'usuari" diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json index f5e41e09ac2..af4beb984a4 100644 --- a/homeassistant/components/asuswrt/translations/de.json +++ b/homeassistant/components/asuswrt/translations/de.json @@ -18,7 +18,7 @@ "mode": "Modus", "name": "Name", "password": "Passwort", - "port": "Port", + "port": "Port (leer lassen f\u00fcr Protokollstandard)", "protocol": "Zu verwendendes Kommunikationsprotokoll", "ssh_key": "Pfad zu deiner SSH-Schl\u00fcsseldatei (anstelle des Passworts)", "username": "Benutzername" diff --git a/homeassistant/components/asuswrt/translations/en.json b/homeassistant/components/asuswrt/translations/en.json index e835bf97538..4bef898ff35 100644 --- a/homeassistant/components/asuswrt/translations/en.json +++ b/homeassistant/components/asuswrt/translations/en.json @@ -18,7 +18,7 @@ "mode": "Mode", "name": "Name", "password": "Password", - "port": "Port", + "port": "Port (leave empty for protocol default)", "protocol": "Communication protocol to use", "ssh_key": "Path to your SSH key file (instead of password)", "username": "Username" diff --git a/homeassistant/components/asuswrt/translations/et.json b/homeassistant/components/asuswrt/translations/et.json index 61e8b1a8d4f..7b7a0869061 100644 --- a/homeassistant/components/asuswrt/translations/et.json +++ b/homeassistant/components/asuswrt/translations/et.json @@ -18,7 +18,7 @@ "mode": "Re\u017eiim", "name": "Nimi", "password": "Salas\u00f5na", - "port": "Port", + "port": "Port (vaikepordi kasutamiseks j\u00e4ta t\u00fchjaks)", "protocol": "Kasutatav sideprotokoll", "ssh_key": "Rada SSH v\u00f5tmefailini (parooli asemel)", "username": "Kasutajanimi" diff --git a/homeassistant/components/asuswrt/translations/fr.json b/homeassistant/components/asuswrt/translations/fr.json index 0d53f3f24cf..5c8882d5813 100644 --- a/homeassistant/components/asuswrt/translations/fr.json +++ b/homeassistant/components/asuswrt/translations/fr.json @@ -18,7 +18,7 @@ "mode": "Mode", "name": "Nom", "password": "Mot de passe", - "port": "Port", + "port": "Port (laisser vide pour utiliser la valeur par d\u00e9faut du protocole)", "protocol": "Protocole de communication \u00e0 utiliser", "ssh_key": "Chemin d'acc\u00e8s \u00e0 votre fichier de cl\u00e9s SSH (au lieu du mot de passe)", "username": "Nom d'utilisateur" diff --git a/homeassistant/components/asuswrt/translations/he.json b/homeassistant/components/asuswrt/translations/he.json index 2d2cebaa7e3..867cc6e4f5c 100644 --- a/homeassistant/components/asuswrt/translations/he.json +++ b/homeassistant/components/asuswrt/translations/he.json @@ -18,7 +18,7 @@ "mode": "\u05de\u05e6\u05d1", "name": "\u05e9\u05dd", "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "port": "\u05e4\u05ea\u05d7\u05d4", + "port": "\u05e4\u05ea\u05d7\u05d4 (\u05e8\u05d9\u05e7 \u05e2\u05d1\u05d5\u05e8 \u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc)", "ssh_key": "\u05e0\u05ea\u05d9\u05d1 \u05dc\u05e7\u05d5\u05d1\u05e5 \u05d4\u05de\u05e4\u05ea\u05d7 \u05e9\u05dc SSH (\u05d1\u05de\u05e7\u05d5\u05dd \u05dc\u05e1\u05d9\u05e1\u05de\u05d4)", "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" } diff --git a/homeassistant/components/asuswrt/translations/hu.json b/homeassistant/components/asuswrt/translations/hu.json index ff64372f1b0..d8133061380 100644 --- a/homeassistant/components/asuswrt/translations/hu.json +++ b/homeassistant/components/asuswrt/translations/hu.json @@ -16,9 +16,9 @@ "data": { "host": "C\u00edm", "mode": "M\u00f3d", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", - "port": "Port", + "port": "Port (hagyja \u00fcresen az alap\u00e9rtelmezetthez)", "protocol": "Haszn\u00e1lhat\u00f3 kommunik\u00e1ci\u00f3s protokoll", "ssh_key": "Az SSH kulcsf\u00e1jl el\u00e9r\u00e9si \u00fatja (jelsz\u00f3 helyett)", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" diff --git a/homeassistant/components/asuswrt/translations/id.json b/homeassistant/components/asuswrt/translations/id.json index aa4eebd1f86..83ade7b6462 100644 --- a/homeassistant/components/asuswrt/translations/id.json +++ b/homeassistant/components/asuswrt/translations/id.json @@ -18,7 +18,7 @@ "mode": "Mode", "name": "Nama", "password": "Kata Sandi", - "port": "Port", + "port": "Port (Biarkan kosong untuk nilai default)", "protocol": "Protokol komunikasi yang akan digunakan", "ssh_key": "Jalur ke file kunci SSH Anda (bukan kata sandi)", "username": "Nama Pengguna" diff --git a/homeassistant/components/asuswrt/translations/it.json b/homeassistant/components/asuswrt/translations/it.json index 824db386176..0cf06679d83 100644 --- a/homeassistant/components/asuswrt/translations/it.json +++ b/homeassistant/components/asuswrt/translations/it.json @@ -7,7 +7,7 @@ "cannot_connect": "Impossibile connettersi", "invalid_host": "Nome host o indirizzo IP non valido", "pwd_and_ssh": "Fornire solo la password o il file della chiave SSH", - "pwd_or_ssh": "Si prega di fornire la password o il file della chiave SSH", + "pwd_or_ssh": "Fornisci la password o il file della chiave SSH", "ssh_not_file": "File chiave SSH non trovato", "unknown": "Errore imprevisto" }, @@ -18,7 +18,7 @@ "mode": "Modalit\u00e0", "name": "Nome", "password": "Password", - "port": "Porta", + "port": "Porta (lascia vuoto per impostazione predefinita del protocollo)", "protocol": "Protocollo di comunicazione da utilizzare", "ssh_key": "Percorso del file della chiave SSH (invece della password)", "username": "Nome utente" diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index f6f347f771f..e6db5fae621 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -18,7 +18,7 @@ "mode": "Mode", "name": "Naam", "password": "Wachtwoord", - "port": "Poort", + "port": "Poort (leeg laten voor protocol standaard)", "protocol": "Te gebruiken communicatieprotocol", "ssh_key": "Pad naar uw SSH-sleutelbestand (in plaats van wachtwoord)", "username": "Gebruikersnaam" diff --git a/homeassistant/components/asuswrt/translations/no.json b/homeassistant/components/asuswrt/translations/no.json index c1e5fd5d99a..84ece53ed42 100644 --- a/homeassistant/components/asuswrt/translations/no.json +++ b/homeassistant/components/asuswrt/translations/no.json @@ -18,7 +18,7 @@ "mode": "Modus", "name": "Navn", "password": "Passord", - "port": "Port", + "port": "Port (la st\u00e5 tomt for protokollstandard)", "protocol": "Kommunikasjonsprotokoll som skal brukes", "ssh_key": "Bane til SSH-n\u00f8kkelfilen (i stedet for passord)", "username": "Brukernavn" diff --git a/homeassistant/components/asuswrt/translations/pl.json b/homeassistant/components/asuswrt/translations/pl.json index 9fd5d00b1c4..76d8f30d950 100644 --- a/homeassistant/components/asuswrt/translations/pl.json +++ b/homeassistant/components/asuswrt/translations/pl.json @@ -18,7 +18,7 @@ "mode": "Tryb", "name": "Nazwa", "password": "Has\u0142o", - "port": "Port", + "port": "Port (pozostaw puste dla domy\u015blnego protoko\u0142u)", "protocol": "Wybierz protok\u00f3\u0142 komunikacyjny", "ssh_key": "\u015acie\u017cka do pliku z kluczem SSH (zamiast has\u0142a)", "username": "Nazwa u\u017cytkownika" diff --git a/homeassistant/components/asuswrt/translations/pt-BR.json b/homeassistant/components/asuswrt/translations/pt-BR.json index 06982ade622..88999128895 100644 --- a/homeassistant/components/asuswrt/translations/pt-BR.json +++ b/homeassistant/components/asuswrt/translations/pt-BR.json @@ -18,7 +18,7 @@ "mode": "Modo", "name": "Nome", "password": "Senha", - "port": "Porta", + "port": "Porta (deixe em branco para o protocolo padr\u00e3o)", "protocol": "Protocolo de comunica\u00e7\u00e3o a ser usado", "ssh_key": "Caminho para seu arquivo de chave SSH (em vez de senha)", "username": "Usu\u00e1rio" diff --git a/homeassistant/components/asuswrt/translations/ru.json b/homeassistant/components/asuswrt/translations/ru.json index 35254821f23..8edc9786f5b 100644 --- a/homeassistant/components/asuswrt/translations/ru.json +++ b/homeassistant/components/asuswrt/translations/ru.json @@ -18,7 +18,7 @@ "mode": "\u0420\u0435\u0436\u0438\u043c", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "port": "\u041f\u043e\u0440\u0442", + "port": "\u041f\u043e\u0440\u0442 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)", "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441\u0432\u044f\u0437\u0438", "ssh_key": "\u041f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443 \u043a\u043b\u044e\u0447\u0435\u0439 SSH (\u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0430\u0440\u043e\u043b\u044f)", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" diff --git a/homeassistant/components/asuswrt/translations/tr.json b/homeassistant/components/asuswrt/translations/tr.json index 9b7870a3fba..3225c78b40a 100644 --- a/homeassistant/components/asuswrt/translations/tr.json +++ b/homeassistant/components/asuswrt/translations/tr.json @@ -18,7 +18,7 @@ "mode": "Mod", "name": "Ad", "password": "Parola", - "port": "Port", + "port": "Port (protokol varsay\u0131lan\u0131 i\u00e7in bo\u015f b\u0131rak\u0131n)", "protocol": "Kullan\u0131lacak ileti\u015fim protokol\u00fc", "ssh_key": "SSH anahtar dosyan\u0131z\u0131n yolu (\u015fifre yerine)", "username": "Kullan\u0131c\u0131 Ad\u0131" diff --git a/homeassistant/components/asuswrt/translations/zh-Hant.json b/homeassistant/components/asuswrt/translations/zh-Hant.json index d0997e495c5..17c5cd698cd 100644 --- a/homeassistant/components/asuswrt/translations/zh-Hant.json +++ b/homeassistant/components/asuswrt/translations/zh-Hant.json @@ -18,7 +18,7 @@ "mode": "\u6a21\u5f0f", "name": "\u540d\u7a31", "password": "\u5bc6\u78bc", - "port": "\u901a\u8a0a\u57e0", + "port": "\u901a\u8a0a\u57e0 (\u4fdd\u6301\u7a7a\u767d\u4f7f\u7528\u9810\u8a2d\u5354\u5b9a)", "protocol": "\u4f7f\u7528\u901a\u8a0a\u606f\u5354\u5b9a", "ssh_key": "SSH \u91d1\u9470\u6a94\u6848\u8def\u5f91\uff08\u975e\u5bc6\u78bc\uff09", "username": "\u4f7f\u7528\u8005\u540d\u7a31" diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index b98a0405bb9..cf5624005ed 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -3,14 +3,11 @@ from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, PRESET_AWAY, PRESET_BOOST, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, Platform @@ -27,8 +24,7 @@ PRESET_MAP = { PRESET_BOOST: "fireplace", } PRESET_INVERTED = {v: k for k, v in PRESET_MAP.items()} -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE -HVAC_MODES = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] +HVAC_MODES = [HVACMode.AUTO, HVACMode.HEAT] async def async_setup_entry( @@ -44,7 +40,9 @@ class AtagThermostat(AtagEntity, ClimateEntity): _attr_hvac_modes = HVAC_MODES _attr_preset_modes = list(PRESET_MAP.keys()) - _attr_supported_features = SUPPORT_FLAGS + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) def __init__(self, coordinator, atag_id): """Initialize an Atag climate device.""" @@ -52,7 +50,7 @@ class AtagThermostat(AtagEntity, ClimateEntity): self._attr_temperature_unit = coordinator.data.climate.temp_unit @property - def hvac_mode(self) -> str | None: # type: ignore[override] + def hvac_mode(self) -> str | None: """Return hvac operation ie. heat, cool mode.""" if self.coordinator.data.climate.hvac_mode in HVAC_MODES: return self.coordinator.data.climate.hvac_mode @@ -62,7 +60,7 @@ class AtagThermostat(AtagEntity, ClimateEntity): def hvac_action(self) -> str | None: """Return the current running hvac operation.""" is_active = self.coordinator.data.climate.status - return CURRENT_HVAC_HEAT if is_active else CURRENT_HVAC_IDLE + return HVACAction.HEATING if is_active else HVACAction.IDLE @property def current_temperature(self) -> float | None: diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index 94d4bfd576a..1dc38401574 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -11,7 +11,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN, AtagEntity -SUPPORT_FLAGS_HEATER = 0 OPERATION_LIST = [STATE_OFF, STATE_ECO, STATE_PERFORMANCE] @@ -29,7 +28,7 @@ class AtagWaterHeater(AtagEntity, WaterHeaterEntity): """Representation of an ATAG water heater.""" _attr_operation_list = OPERATION_LIST - _attr_supported_features = SUPPORT_FLAGS_HEATER + _attr_supported_features = 0 _attr_temperature_unit = TEMP_CELSIUS @property diff --git a/homeassistant/components/aurora/translations/hu.json b/homeassistant/components/aurora/translations/hu.json index 292ed552235..cbca495254d 100644 --- a/homeassistant/components/aurora/translations/hu.json +++ b/homeassistant/components/aurora/translations/hu.json @@ -8,7 +8,7 @@ "data": { "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" } } } @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "threshold": "K\u00fcsz\u00f6b (%)" + "threshold": "K\u00fcsz\u00f6b\u00e9rt\u00e9k (%)" } } } diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 4ddfccd032c..30b36a40f32 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -113,6 +113,12 @@ Result will be a long-lived access token: "result": "ABCDEFGH" } + +# POST /auth/external/callback + +This is an endpoint for OAuth2 Authorization callbacks used by integrations +that link accounts with other cloud providers using LocalOAuth2Implementation +as part of a config flow. """ from __future__ import annotations @@ -134,6 +140,7 @@ from homeassistant.components.http.ban import log_invalid_auth from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2AuthorizeCallbackView from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util @@ -195,6 +202,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.http.register_view(TokenView(retrieve_result)) hass.http.register_view(LinkUserView(retrieve_result)) + hass.http.register_view(OAuth2AuthorizeCallbackView()) websocket_api.async_register_command( hass, WS_TYPE_CURRENT_USER, websocket_current_user, SCHEMA_WS_CURRENT_USER @@ -263,7 +271,6 @@ class TokenView(HomeAssistantView): async def _async_handle_revoke_token(self, hass, data): """Handle revoke token request.""" - # pylint: disable=no-self-use # OAuth 2.0 Token Revocation [RFC7009] # 2.2 The authorization server responds with HTTP status code 200 diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 722dc29438e..cd6a405d42c 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -198,7 +198,6 @@ class LoginFlowIndexView(LoginFlowBaseView): async def get(self, request): """Do not allow index of flows in progress.""" - # pylint: disable=no-self-use return web.Response(status=HTTPStatus.METHOD_NOT_ALLOWED) @RequestDataValidator( diff --git a/homeassistant/components/auth/translations/fr.json b/homeassistant/components/auth/translations/fr.json index cf0a1888495..79b467d0255 100644 --- a/homeassistant/components/auth/translations/fr.json +++ b/homeassistant/components/auth/translations/fr.json @@ -5,7 +5,7 @@ "no_available_service": "Aucun service de notification disponible." }, "error": { - "invalid_code": "Code invalide. Veuillez essayer \u00e0 nouveau." + "invalid_code": "Code non valide, veuillez r\u00e9essayer." }, "step": { "init": { @@ -21,7 +21,7 @@ }, "totp": { "error": { - "invalid_code": "Code invalide. Veuillez essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte." + "invalid_code": "Code non valide, veuillez r\u00e9essayer. Si cette erreur persiste, assurez-vous que l'heure de votre syst\u00e8me Home Assistant est correcte." }, "step": { "init": { diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3c9cd07a146..c743e1f83fd 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -17,6 +17,7 @@ from homeassistant.const import ( CONF_CONDITION, CONF_DEVICE_ID, CONF_ENTITY_ID, + CONF_EVENT_DATA, CONF_ID, CONF_MODE, CONF_PLATFORM, @@ -47,6 +48,9 @@ from homeassistant.helpers import condition, extract_domain_configs import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import ( ATTR_CUR, @@ -227,6 +231,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up all automations.""" hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + # To register the automation blueprints async_get_blueprints(hass) @@ -353,9 +361,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): referenced |= condition.async_extract_devices(conf) for conf in self._trigger_config: - device = _trigger_extract_device(conf) - if device is not None: - referenced.add(device) + referenced |= set(_trigger_extract_device(conf)) self._referenced_devices = referenced return referenced @@ -757,12 +763,22 @@ async def _async_process_if(hass, name, config, p_config): @callback -def _trigger_extract_device(trigger_conf: dict) -> str | None: +def _trigger_extract_device(trigger_conf: dict) -> list[str]: """Extract devices from a trigger config.""" - if trigger_conf[CONF_PLATFORM] != "device": - return None + if trigger_conf[CONF_PLATFORM] == "device": + return [trigger_conf[CONF_DEVICE_ID]] - return trigger_conf[CONF_DEVICE_ID] + if ( + trigger_conf[CONF_PLATFORM] == "event" + and CONF_EVENT_DATA in trigger_conf + and CONF_DEVICE_ID in trigger_conf[CONF_EVENT_DATA] + ): + return [trigger_conf[CONF_EVENT_DATA][CONF_DEVICE_ID]] + + if trigger_conf[CONF_PLATFORM] == "tag" and CONF_DEVICE_ID in trigger_conf: + return trigger_conf[CONF_DEVICE_ID] + + return [] @callback @@ -771,6 +787,9 @@ def _trigger_extract_entities(trigger_conf: dict) -> list[str]: if trigger_conf[CONF_PLATFORM] in ("state", "numeric_state"): return trigger_conf[CONF_ENTITY_ID] + if trigger_conf[CONF_PLATFORM] == "calendar": + return [trigger_conf[CONF_ENTITY_ID]] + if trigger_conf[CONF_PLATFORM] == "zone": return trigger_conf[CONF_ENTITY_ID] + [trigger_conf[CONF_ZONE]] @@ -780,4 +799,11 @@ def _trigger_extract_entities(trigger_conf: dict) -> list[str]: if trigger_conf[CONF_PLATFORM] == "sun": return ["sun.sun"] + if ( + trigger_conf[CONF_PLATFORM] == "event" + and CONF_EVENT_DATA in trigger_conf + and CONF_ENTITY_ID in trigger_conf[CONF_EVENT_DATA] + ): + return [trigger_conf[CONF_EVENT_DATA][CONF_ENTITY_ID]] + return [] diff --git a/homeassistant/components/automation/recorder.py b/homeassistant/components/automation/recorder.py new file mode 100644 index 00000000000..3083d271d1f --- /dev/null +++ b/homeassistant/components/automation/recorder.py @@ -0,0 +1,12 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_CUR, ATTR_LAST_TRIGGERED, ATTR_MAX, ATTR_MODE, CONF_ID + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude extra attributes from being recorded in the database.""" + return {ATTR_LAST_TRIGGERED, ATTR_MODE, ATTR_CUR, ATTR_MAX, CONF_ID} diff --git a/homeassistant/components/avea/light.py b/homeassistant/components/avea/light.py index 56dfd4878ff..31054e2e287 100644 --- a/homeassistant/components/avea/light.py +++ b/homeassistant/components/avea/light.py @@ -6,8 +6,7 @@ import avea # pylint: disable=import-error from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.core import HomeAssistant @@ -16,8 +15,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.color as color_util -SUPPORT_AVEA = SUPPORT_BRIGHTNESS | SUPPORT_COLOR - def setup_platform( hass: HomeAssistant, @@ -40,7 +37,8 @@ def setup_platform( class AveaLight(LightEntity): """Representation of an Avea.""" - _attr_supported_features = SUPPORT_AVEA + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} def __init__(self, light): """Initialize an AveaLight.""" diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index 7a57efe6aa6..7df17c2e74e 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import ( @@ -25,8 +25,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -SUPPORT_AVION_LED = SUPPORT_BRIGHTNESS - DEVICE_SCHEMA = vol.Schema( { vol.Required(CONF_API_KEY): cv.string, @@ -75,7 +73,8 @@ def setup_platform( class AvionLight(LightEntity): """Representation of an Avion light.""" - _attr_supported_features = SUPPORT_AVION_LED + _attr_support_color_mode = ColorMode.BRIGHTNESS + _attr_support_color_modes = {ColorMode.BRIGHTNESS} _attr_should_poll = False _attr_assumed_state = True _attr_is_on = True diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py index c82c7ed19bd..2364b85a63e 100644 --- a/homeassistant/components/awair/__init__.py +++ b/homeassistant/components/awair/__init__.py @@ -76,7 +76,6 @@ class AwairDataUpdateCoordinator(DataUpdateCoordinator): async def _fetch_air_data(self, device): """Fetch latest air quality data.""" - # pylint: disable=no-self-use LOGGER.debug("Fetching data for %s", device.uuid) air_data = await device.air_data_latest() LOGGER.debug(air_data) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 2c9a6a52b9e..bd46ae54f81 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -1,7 +1,7 @@ """Support for Axis camera streaming.""" from urllib.parse import urlencode -from homeassistant.components.camera import SUPPORT_STREAM +from homeassistant.components.camera import CameraEntityFeature from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging from homeassistant.config_entries import ConfigEntry from homeassistant.const import HTTP_DIGEST_AUTHENTICATION @@ -32,6 +32,8 @@ async def async_setup_entry( class AxisCamera(AxisEntityBase, MjpegCamera): """Representation of a Axis camera.""" + _attr_supported_features = CameraEntityFeature.STREAM + def __init__(self, device): """Initialize Axis Communications camera component.""" AxisEntityBase.__init__(self, device) @@ -58,11 +60,6 @@ class AxisCamera(AxisEntityBase, MjpegCamera): await super().async_added_to_hass() - @property - def supported_features(self) -> int: - """Return supported features.""" - return SUPPORT_STREAM - def _new_address(self) -> None: """Set new device address for video stream.""" self._mjpeg_url = self.mjpeg_source diff --git a/homeassistant/components/axis/light.py b/homeassistant/components/axis/light.py index caa7880734c..30f1cee9340 100644 --- a/homeassistant/components/axis/light.py +++ b/homeassistant/components/axis/light.py @@ -1,11 +1,7 @@ """Support for Axis lights.""" from axis.event_stream import CLASS_LIGHT -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - COLOR_MODE_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -59,8 +55,8 @@ class AxisLight(AxisEventBase, LightEntity): light_type = device.api.vapix.light_control[self.light_id].light_type self._attr_name = f"{device.name} {light_type} {event.TYPE} {event.id}" - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS async def async_added_to_hass(self) -> None: """Subscribe lights events.""" diff --git a/homeassistant/components/axis/translations/hu.json b/homeassistant/components/axis/translations/hu.json index cb2f9a17c93..c9121ed2f54 100644 --- a/homeassistant/components/axis/translations/hu.json +++ b/homeassistant/components/axis/translations/hu.json @@ -7,7 +7,7 @@ }, "error": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, diff --git a/homeassistant/components/azure_devops/translations/it.json b/homeassistant/components/azure_devops/translations/it.json index 232264d1026..bd14e7926cd 100644 --- a/homeassistant/components/azure_devops/translations/it.json +++ b/homeassistant/components/azure_devops/translations/it.json @@ -21,10 +21,10 @@ "user": { "data": { "organization": "Organizzazione", - "personal_access_token": "Token di Accesso Personale (PAT)", + "personal_access_token": "Token di accesso personale (PAT)", "project": "Progetto" }, - "description": "Configura un'istanza di DevOps di Azure per accedere al progetto. Un Token di Accesso Personale (PAT) \u00e8 richiesto solo per un progetto privato.", + "description": "Configura un'istanza di DevOps di Azure per accedere al progetto. Un token di accesso personale (PAT) \u00e8 richiesto solo per un progetto privato.", "title": "Aggiungere un progetto Azure DevOps" } } diff --git a/homeassistant/components/azure_event_hub/translations/de.json b/homeassistant/components/azure_event_hub/translations/de.json index c1552371f76..b72ef47ab34 100644 --- a/homeassistant/components/azure_event_hub/translations/de.json +++ b/homeassistant/components/azure_event_hub/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Der Dienst ist bereits konfiguriert", "cannot_connect": "Die Verbindung mit den Anmeldeinformationen aus der configuration.yaml ist fehlgeschlagen, bitte entferne diese aus der yaml und verwende den Konfigurationsfluss.", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", - "unknown": "Die Verbindung mit den Anmeldeinformationen aus der configuration.yaml ist mit einem unbekannten Fehler fehlgeschlagen. Bitte entferne diese aus der yaml und verwende den Konfigurationsfluss." + "unknown": "Die Verbindung mit den Anmeldeinformationen aus der configuration.yaml ist mit einem unbekannten Fehler fehlgeschlagen. Bitte entferne diese aus der yaml und verwende den Konfigurationsfluss." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/backup/__init__.py b/homeassistant/components/backup/__init__.py index 711051507d4..8d5f9764959 100644 --- a/homeassistant/components/backup/__init__.py +++ b/homeassistant/components/backup/__init__.py @@ -1,6 +1,6 @@ """The Backup integration.""" from homeassistant.components.hassio import is_hassio -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -18,7 +18,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) return False - hass.data[DOMAIN] = BackupManager(hass) + backup_manager = BackupManager(hass) + hass.data[DOMAIN] = backup_manager + + async def async_handle_create_service(call: ServiceCall) -> None: + """Service handler for creating backups.""" + await backup_manager.generate_backup() + + hass.services.async_register(DOMAIN, "create", async_handle_create_service) async_register_websocket_handlers(hass) async_register_http_views(hass) diff --git a/homeassistant/components/backup/http.py b/homeassistant/components/backup/http.py index 6f1f0d00167..a6a7a2346e4 100644 --- a/homeassistant/components/backup/http.py +++ b/homeassistant/components/backup/http.py @@ -26,7 +26,7 @@ class DownloadBackupView(HomeAssistantView): url = "/api/backup/download/{slug}" name = "api:backup:download" - async def get( # pylint: disable=no-self-use + async def get( self, request: Request, slug: str, diff --git a/homeassistant/components/backup/services.yaml b/homeassistant/components/backup/services.yaml new file mode 100644 index 00000000000..d001c57ef5c --- /dev/null +++ b/homeassistant/components/backup/services.yaml @@ -0,0 +1,3 @@ +create: + name: Create backup + description: Create a new backup. diff --git a/homeassistant/components/balboa/climate.py b/homeassistant/components/balboa/climate.py index 81016bbeb33..98acf88649e 100644 --- a/homeassistant/components/balboa/climate.py +++ b/homeassistant/components/balboa/climate.py @@ -5,18 +5,13 @@ import asyncio from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -70,18 +65,20 @@ class BalboaSpaClimate(BalboaEntity, ClimateEntity): value: key for key, value in self._balboa_to_ha_blower_map.items() } self._balboa_to_ha_heatmode_map = { - self._client.HEATMODE_READY: HVAC_MODE_HEAT, - self._client.HEATMODE_RNR: HVAC_MODE_AUTO, - self._client.HEATMODE_REST: HVAC_MODE_OFF, + self._client.HEATMODE_READY: HVACMode.HEAT, + self._client.HEATMODE_RNR: HVACMode.AUTO, + self._client.HEATMODE_REST: HVACMode.OFF, } self._ha_heatmode_to_balboa_map = { value: key for key, value in self._balboa_to_ha_heatmode_map.items() } scale = self._client.get_tempscale() self._attr_preset_modes = self._client.get_heatmode_stringlist() - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + self._attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) if self._client.have_blower(): - self._attr_supported_features |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE self._attr_min_temp = self._client.tmin[self._client.TEMPRANGE_LOW][scale] self._attr_max_temp = self._client.tmax[self._client.TEMPRANGE_HIGH][scale] self._attr_temperature_unit = TEMP_FAHRENHEIT @@ -100,8 +97,8 @@ class BalboaSpaClimate(BalboaEntity, ClimateEntity): def hvac_action(self) -> str: """Return the current operation mode.""" if self._client.get_heatstate() >= self._client.ON: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return HVACAction.HEATING + return HVACAction.IDLE @property def fan_mode(self) -> str: diff --git a/homeassistant/components/balboa/const.py b/homeassistant/components/balboa/const.py index 9fb0b34d003..9a2d6b704ff 100644 --- a/homeassistant/components/balboa/const.py +++ b/homeassistant/components/balboa/const.py @@ -1,4 +1,6 @@ """Constants for the Balboa Spa Client integration.""" +from __future__ import annotations + import logging from homeassistant.components.climate.const import ( @@ -6,9 +8,7 @@ from homeassistant.components.climate.const import ( FAN_LOW, FAN_MEDIUM, FAN_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, + HVACMode, ) from homeassistant.const import Platform @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "balboa" CLIMATE_SUPPORTED_FANSTATES = [FAN_OFF, FAN_LOW, FAN_MEDIUM, FAN_HIGH] -CLIMATE_SUPPORTED_MODES = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] +CLIMATE_SUPPORTED_MODES = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF] CONF_SYNC_TIME = "sync_time" DEFAULT_SYNC_TIME = False PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE] diff --git a/homeassistant/components/binary_sensor/translations/cs.json b/homeassistant/components/binary_sensor/translations/cs.json index fb19cc00fda..ced02ffc326 100644 --- a/homeassistant/components/binary_sensor/translations/cs.json +++ b/homeassistant/components/binary_sensor/translations/cs.json @@ -112,6 +112,10 @@ "off": "Nenab\u00edj\u00ed se", "on": "Nab\u00edjen\u00ed" }, + "carbon_monoxide": { + "off": "\u017d\u00e1dn\u00fd plyn", + "on": "Zji\u0161t\u011bn plyn" + }, "cold": { "off": "Norm\u00e1ln\u00ed", "on": "Studen\u00e9" diff --git a/homeassistant/components/binary_sensor/translations/he.json b/homeassistant/components/binary_sensor/translations/he.json index 293a4f3f264..5f0e14ccac1 100644 --- a/homeassistant/components/binary_sensor/translations/he.json +++ b/homeassistant/components/binary_sensor/translations/he.json @@ -151,11 +151,11 @@ "on": "\u05de\u05d7\u05d5\u05d1\u05e8" }, "door": { - "off": "\u05e1\u05d2\u05d5\u05e8", + "off": "\u05e0\u05e1\u05d2\u05e8", "on": "\u05e4\u05ea\u05d5\u05d7" }, "garage_door": { - "off": "\u05e1\u05d2\u05d5\u05e8", + "off": "\u05e0\u05e1\u05d2\u05e8", "on": "\u05e4\u05ea\u05d5\u05d7" }, "gas": { @@ -191,7 +191,7 @@ "on": "\u05d6\u05d5\u05d4\u05d4" }, "opening": { - "off": "\u05e1\u05d2\u05d5\u05e8", + "off": "\u05e0\u05e1\u05d2\u05e8", "on": "\u05e4\u05ea\u05d5\u05d7" }, "plug": { @@ -231,7 +231,7 @@ "on": "\u05d6\u05d5\u05d4\u05d4" }, "window": { - "off": "\u05e1\u05d2\u05d5\u05e8", + "off": "\u05e0\u05e1\u05d2\u05e8", "on": "\u05e4\u05ea\u05d5\u05d7" } }, diff --git a/homeassistant/components/binary_sensor/translations/pt-BR.json b/homeassistant/components/binary_sensor/translations/pt-BR.json index d858dec6664..5b1ba2a1abf 100644 --- a/homeassistant/components/binary_sensor/translations/pt-BR.json +++ b/homeassistant/components/binary_sensor/translations/pt-BR.json @@ -179,7 +179,7 @@ "on": "Molhado" }, "motion": { - "off": "Sem movimento", + "off": "N\u00e3o detectado", "on": "Detectado" }, "moving": { diff --git a/homeassistant/components/binary_sensor/translations/zh-Hant.json b/homeassistant/components/binary_sensor/translations/zh-Hant.json index a0adaaab083..417ca652cb5 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hant.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hant.json @@ -203,7 +203,7 @@ "on": "\u5728\u5bb6" }, "problem": { - "off": "\u78ba\u5b9a", + "off": "\u6b63\u5e38", "on": "\u7570\u5e38" }, "running": { diff --git a/homeassistant/components/bitcoin/manifest.json b/homeassistant/components/bitcoin/manifest.json index 2cd9453f4b8..b536c251c96 100644 --- a/homeassistant/components/bitcoin/manifest.json +++ b/homeassistant/components/bitcoin/manifest.json @@ -3,7 +3,7 @@ "name": "Bitcoin", "documentation": "https://www.home-assistant.io/integrations/bitcoin", "requirements": ["blockchain==1.4.4"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_polling", "loggers": ["blockchain"] } diff --git a/homeassistant/components/blackbird/media_player.py b/homeassistant/components/blackbird/media_player.py index b0fb9b785dd..a20c4294438 100644 --- a/homeassistant/components/blackbird/media_player.py +++ b/homeassistant/components/blackbird/media_player.py @@ -8,11 +8,10 @@ from pyblackbird import get_blackbird from serial import SerialException import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -32,8 +31,6 @@ from .const import DOMAIN, SERVICE_SETALLZONES _LOGGER = logging.getLogger(__name__) -SUPPORT_BLACKBIRD = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE - MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids}) ZONE_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) @@ -141,7 +138,11 @@ def setup_platform( class BlackbirdZone(MediaPlayerEntity): """Representation of a Blackbird matrix zone.""" - _attr_supported_features = SUPPORT_BLACKBIRD + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) def __init__(self, blackbird, sources, zone_id, zone_name): """Initialize new zone.""" diff --git a/homeassistant/components/blebox/climate.py b/homeassistant/components/blebox/climate.py index 1b864bd5537..20a019ec0ec 100644 --- a/homeassistant/components/blebox/climate.py +++ b/homeassistant/components/blebox/climate.py @@ -1,12 +1,9 @@ """BleBox climate entity.""" from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -31,8 +28,8 @@ async def async_setup_entry( class BleBoxClimateEntity(BleBoxEntity, ClimateEntity): """Representation of a BleBox climate feature (saunaBox).""" - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE - _attr_hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_HEAT] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT] _attr_temperature_unit = TEMP_CELSIUS @property @@ -41,16 +38,16 @@ class BleBoxClimateEntity(BleBoxEntity, ClimateEntity): if self._feature.is_on is None: return None - return HVAC_MODE_HEAT if self._feature.is_on else HVAC_MODE_OFF + return HVACMode.HEAT if self._feature.is_on else HVACMode.OFF @property def hvac_action(self): """Return the actual current HVAC action.""" if not (is_on := self._feature.is_on): - return None if is_on is None else CURRENT_HVAC_OFF + return None if is_on is None else HVACAction.OFF # NOTE: In practice, there's no need to handle case when is_heating is None - return CURRENT_HVAC_HEAT if self._feature.is_heating else CURRENT_HVAC_IDLE + return HVACAction.HEATING if self._feature.is_heating else HVACAction.IDLE @property def max_temp(self): @@ -74,7 +71,7 @@ class BleBoxClimateEntity(BleBoxEntity, ClimateEntity): async def async_set_hvac_mode(self, hvac_mode): """Set the climate entity mode.""" - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: await self._feature.async_on() return diff --git a/homeassistant/components/blebox/cover.py b/homeassistant/components/blebox/cover.py index 3056144b2e6..09d28a4f87a 100644 --- a/homeassistant/components/blebox/cover.py +++ b/homeassistant/components/blebox/cover.py @@ -1,11 +1,8 @@ """BleBox cover entity.""" from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING @@ -35,9 +32,11 @@ class BleBoxCoverEntity(BleBoxEntity, CoverEntity): """Initialize a BleBox cover feature.""" super().__init__(feature) self._attr_device_class = BLEBOX_TO_HASS_DEVICE_CLASSES[feature.device_class] - position = SUPPORT_SET_POSITION if feature.is_slider else 0 - stop = SUPPORT_STOP if feature.has_stop else 0 - self._attr_supported_features = position | stop | SUPPORT_OPEN | SUPPORT_CLOSE + position = CoverEntityFeature.SET_POSITION if feature.is_slider else 0 + stop = CoverEntityFeature.STOP if feature.has_stop else 0 + self._attr_supported_features = ( + position | stop | CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + ) @property def current_cover_position(self): diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py index 48e8ec3f4bb..a582fe133c1 100644 --- a/homeassistant/components/blebox/light.py +++ b/homeassistant/components/blebox/light.py @@ -6,9 +6,7 @@ from blebox_uniapi.error import BadOnValueError from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGBW_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_ONOFF, - COLOR_MODE_RGBW, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -55,10 +53,10 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): def color_mode(self): """Return the color mode.""" if self._feature.supports_white and self._feature.supports_color: - return COLOR_MODE_RGBW + return ColorMode.RGBW if self._feature.supports_brightness: - return COLOR_MODE_BRIGHTNESS - return COLOR_MODE_ONOFF + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF @property def rgbw_color(self): diff --git a/homeassistant/components/blebox/translations/it.json b/homeassistant/components/blebox/translations/it.json index 6d377840e90..86025ffdeee 100644 --- a/homeassistant/components/blebox/translations/it.json +++ b/homeassistant/components/blebox/translations/it.json @@ -7,7 +7,7 @@ "error": { "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto", - "unsupported_version": "Il dispositivo BleBox ha un firmware obsoleto. Si prega di aggiornarlo prima." + "unsupported_version": "Il dispositivo BleBox ha un firmware obsoleto. Aggiornalo prima." }, "flow_title": "{name} ({host})", "step": { diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index 52c05110394..dbea1371af2 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -1,8 +1,10 @@ """Support for Blink Alarm Control Panel.""" import logging -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import SUPPORT_ALARM_ARM_AWAY +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -36,7 +38,7 @@ class BlinkSyncModule(AlarmControlPanelEntity): """Representation of a Blink Alarm Control Panel.""" _attr_icon = ICON - _attr_supported_features = SUPPORT_ALARM_ARM_AWAY + _attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY def __init__(self, data, name, sync): """Initialize the alarm control panel.""" diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index 419ff40945c..429452cf4bd 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -3,6 +3,8 @@ from __future__ import annotations import logging +from requests.exceptions import ChunkedEncodingError + from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -85,4 +87,11 @@ class BlinkCamera(Camera): self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return a still image response from the camera.""" - return self._camera.image_from_cache.content + try: + return self._camera.image_from_cache.content + except ChunkedEncodingError: + _LOGGER.debug("Could not retrieve image for %s", self._camera.name) + return None + except TypeError: + _LOGGER.debug("No cached image for %s", self._camera.name) + return None diff --git a/homeassistant/components/blinksticklight/light.py b/homeassistant/components/blinksticklight/light.py index 3a2410e77cf..621bd01f874 100644 --- a/homeassistant/components/blinksticklight/light.py +++ b/homeassistant/components/blinksticklight/light.py @@ -8,8 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.const import CONF_NAME @@ -23,8 +22,6 @@ CONF_SERIAL = "serial" DEFAULT_NAME = "Blinkstick" -SUPPORT_BLINKSTICK = SUPPORT_BRIGHTNESS | SUPPORT_COLOR - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_SERIAL): cv.string, @@ -52,7 +49,8 @@ def setup_platform( class BlinkStickLight(LightEntity): """Representation of a BlinkStick light.""" - _attr_supported_features = SUPPORT_BLINKSTICK + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} def __init__(self, stick, name): """Initialize the light.""" diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 443d8faa5de..32f74743972 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -16,27 +16,17 @@ import voluptuous as vol import xmltodict from homeassistant.components import media_source -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -803,34 +793,41 @@ class BluesoundPlayer(MediaPlayerEntity): return 0 if self.is_grouped and not self.is_master: - return SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE + return ( + MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + ) - supported = SUPPORT_CLEAR_PLAYLIST | SUPPORT_BROWSE_MEDIA + supported = ( + MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) if self._status.get("indexing", "0") == "0": supported = ( supported - | SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_PLAY - | SUPPORT_SELECT_SOURCE - | SUPPORT_SHUFFLE_SET + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.SHUFFLE_SET ) current_vol = self.volume_level if current_vol is not None and current_vol >= 0: supported = ( supported - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE ) if self._status.get("canSeek", "") == "1": - supported = supported | SUPPORT_SEEK + supported = supported | MediaPlayerEntityFeature.SEEK return supported diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 232788b21d7..dae211f91a2 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -1,47 +1,30 @@ """Reads vehicle status from BMW connected drive portal.""" from __future__ import annotations -from collections.abc import Callable -import logging -from typing import Any, cast +from typing import Any -from bimmer_connected.account import ConnectedDriveAccount -from bimmer_connected.country_selector import get_region_from_name from bimmer_connected.vehicle import ConnectedDriveVehicle import voluptuous as vol -from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DEVICE_ID, + CONF_ENTITY_ID, CONF_NAME, CONF_PASSWORD, CONF_REGION, CONF_USERNAME, Platform, ) -from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry, discovery +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import DeviceInfo, Entity -from homeassistant.helpers.event import track_utc_time_change from homeassistant.helpers.typing import ConfigType -from homeassistant.util import slugify -import homeassistant.util.dt as dt_util +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ( - ATTRIBUTION, - CONF_ACCOUNT, - CONF_READ_ONLY, - DATA_ENTRIES, - DATA_HASS_CONFIG, -) - -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "bmw_connected_drive" -ATTR_VIN = "vin" +from .const import ATTR_VIN, ATTRIBUTION, CONF_READ_ONLY, DATA_HASS_CONFIG, DOMAIN +from .coordinator import BMWDataUpdateCoordinator CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) @@ -64,20 +47,9 @@ PLATFORMS = [ Platform.NOTIFY, Platform.SENSOR, ] -UPDATE_INTERVAL = 5 # in minutes SERVICE_UPDATE_STATE = "update_state" -_SERVICE_MAP = { - "light_flash": "trigger_remote_light_flash", - "sound_horn": "trigger_remote_horn", - "activate_air_conditioning": "trigger_remote_air_conditioning", - "deactivate_air_conditioning": "trigger_remote_air_conditioning_stop", - "find_vehicle": "trigger_remote_vehicle_finder", -} - -UNDO_UPDATE_LISTENER = "undo_update_listener" - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the BMW Connected Drive component from configuration.yaml.""" @@ -104,40 +76,23 @@ def _async_migrate_options_from_data_if_missing( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up BMW Connected Drive from a config entry.""" - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN].setdefault(DATA_ENTRIES, {}) _async_migrate_options_from_data_if_missing(hass, entry) - try: - account = await hass.async_add_executor_job( - setup_account, entry, hass, entry.data[CONF_USERNAME] - ) - except OSError as ex: - raise ConfigEntryNotReady from ex + # Set up one data coordinator per account/config entry + coordinator = BMWDataUpdateCoordinator( + hass, + username=entry.data[CONF_USERNAME], + password=entry.data[CONF_PASSWORD], + region=entry.data[CONF_REGION], + read_only=entry.options[CONF_READ_ONLY], + ) + await coordinator.async_config_entry_first_refresh() - async def _async_update_all(service_call: ServiceCall | None = None) -> None: - """Update all BMW accounts.""" - await hass.async_add_executor_job(_update_all) - - def _update_all() -> None: - """Update all BMW accounts.""" - for entry in hass.data[DOMAIN][DATA_ENTRIES].copy().values(): - entry[CONF_ACCOUNT].update() - - # Add update listener for config entry changes (options) - undo_listener = entry.add_update_listener(update_listener) - - hass.data[DOMAIN][DATA_ENTRIES][entry.entry_id] = { - CONF_ACCOUNT: account, - UNDO_UPDATE_LISTENER: undo_listener, - } - - # Service to manually trigger updates for all accounts. - hass.services.async_register(DOMAIN, SERVICE_UPDATE_STATE, _async_update_all) - - await _async_update_all() + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = coordinator + # Set up all platforms except notify hass.config_entries.async_setup_platforms( entry, [platform for platform in PLATFORMS if platform != Platform.NOTIFY] ) @@ -149,11 +104,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, Platform.NOTIFY, DOMAIN, - {CONF_NAME: DOMAIN}, + {CONF_NAME: DOMAIN, CONF_ENTITY_ID: entry.entry_id}, hass.data[DOMAIN][DATA_HASS_CONFIG], ) ) + # Add event listener for option flow changes + entry.async_on_unload(entry.add_update_listener(async_update_options)) + return True @@ -163,200 +121,44 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry, [platform for platform in PLATFORMS if platform != Platform.NOTIFY] ) - # Only remove services if it is the last account and not read only - if ( - len(hass.data[DOMAIN][DATA_ENTRIES]) == 1 - and not hass.data[DOMAIN][DATA_ENTRIES][entry.entry_id][CONF_ACCOUNT].read_only - ): - services = list(_SERVICE_MAP) + [SERVICE_UPDATE_STATE] - for service in services: - hass.services.async_remove(DOMAIN, service) - - for vehicle in hass.data[DOMAIN][DATA_ENTRIES][entry.entry_id][ - CONF_ACCOUNT - ].account.vehicles: - hass.services.async_remove(NOTIFY_DOMAIN, slugify(f"{DOMAIN}_{vehicle.name}")) - if unload_ok: - hass.data[DOMAIN][DATA_ENTRIES][entry.entry_id][UNDO_UPDATE_LISTENER]() - hass.data[DOMAIN][DATA_ENTRIES].pop(entry.entry_id) + hass.data[DOMAIN].pop(entry.entry_id) return unload_ok -async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(config_entry.entry_id) -def setup_account( - entry: ConfigEntry, hass: HomeAssistant, name: str -) -> BMWConnectedDriveAccount: - """Set up a new BMWConnectedDriveAccount based on the config.""" - username: str = entry.data[CONF_USERNAME] - password: str = entry.data[CONF_PASSWORD] - region: str = entry.data[CONF_REGION] - read_only: bool = entry.options[CONF_READ_ONLY] - - _LOGGER.debug("Adding new account %s", name) - - pos = (hass.config.latitude, hass.config.longitude) - cd_account = BMWConnectedDriveAccount( - username, password, region, name, read_only, *pos - ) - - def execute_service(call: ServiceCall) -> None: - """Execute a service for a vehicle.""" - _LOGGER.warning( - "BMW Connected Drive services are deprecated. Please migrate to the dedicated button entities. " - "See https://www.home-assistant.io/integrations/bmw_connected_drive/#buttons for details" - ) - - vin: str | None = call.data.get(ATTR_VIN) - device_id: str | None = call.data.get(CONF_DEVICE_ID) - - vehicle: ConnectedDriveVehicle | None = None - - if not vin and device_id: - # If vin is None, device_id must be set (given by SERVICE_SCHEMA) - if not (device := device_registry.async_get(hass).async_get(device_id)): - _LOGGER.error("Could not find a device for id: %s", device_id) - return - vin = next(iter(device.identifiers))[1] - else: - vin = cast(str, vin) - - # Double check for read_only accounts as another account could create the services - for entry_data in [ - e - for e in hass.data[DOMAIN][DATA_ENTRIES].values() - if not e[CONF_ACCOUNT].read_only - ]: - account: ConnectedDriveAccount = entry_data[CONF_ACCOUNT].account - if vehicle := account.get_vehicle(vin): - break - if not vehicle: - _LOGGER.error("Could not find a vehicle for VIN %s", vin) - return - function_name = _SERVICE_MAP[call.service] - function_call = getattr(vehicle.remote_services, function_name) - function_call() - - if call.service in [ - "find_vehicle", - "activate_air_conditioning", - "deactivate_air_conditioning", - ]: - cd_account.update() - - if not read_only: - # register the remote services - for service in _SERVICE_MAP: - hass.services.register( - DOMAIN, service, execute_service, schema=SERVICE_SCHEMA - ) - - # update every UPDATE_INTERVAL minutes, starting now - # this should even out the load on the servers - now = dt_util.utcnow() - track_utc_time_change( - hass, - cd_account.update, - minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL), - second=now.second, - ) - - # Initialize - cd_account.update() - - return cd_account - - -class BMWConnectedDriveAccount: - """Representation of a BMW vehicle.""" - - def __init__( - self, - username: str, - password: str, - region_str: str, - name: str, - read_only: bool, - lat: float | None = None, - lon: float | None = None, - ) -> None: - """Initialize account.""" - region = get_region_from_name(region_str) - - self.read_only = read_only - self.account = ConnectedDriveAccount(username, password, region) - self.name = name - self._update_listeners: list[Callable[[], None]] = [] - - # Set observer position once for older cars to be in range for - # GPS position (pre-7/2014, <2km) and get new data from API - if lat and lon: - self.account.set_observer_position(lat, lon) - self.account.update_vehicle_states() - - def update(self, *_: Any) -> None: - """Update the state of all vehicles. - - Notify all listeners about the update. - """ - _LOGGER.debug( - "Updating vehicle state for account %s, notifying %d listeners", - self.name, - len(self._update_listeners), - ) - try: - self.account.update_vehicle_states() - for listener in self._update_listeners: - listener() - except OSError as exception: - _LOGGER.error( - "Could not connect to the BMW Connected Drive portal. " - "The vehicle state could not be updated" - ) - _LOGGER.exception(exception) - - def add_update_listener(self, listener: Callable[[], None]) -> None: - """Add a listener for update notifications.""" - self._update_listeners.append(listener) - - -class BMWConnectedDriveBaseEntity(Entity): +class BMWConnectedDriveBaseEntity(CoordinatorEntity[BMWDataUpdateCoordinator], Entity): """Common base for BMW entities.""" - _attr_should_poll = False _attr_attribution = ATTRIBUTION def __init__( self, - account: BMWConnectedDriveAccount, + coordinator: BMWDataUpdateCoordinator, vehicle: ConnectedDriveVehicle, ) -> None: - """Initialize sensor.""" - self._account = account - self._vehicle = vehicle + """Initialize entity.""" + super().__init__(coordinator) + + self.vehicle = vehicle + self._attrs: dict[str, Any] = { - "car": self._vehicle.name, - "vin": self._vehicle.vin, + "car": self.vehicle.name, + "vin": self.vehicle.vin, } self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, vehicle.vin)}, + identifiers={(DOMAIN, self.vehicle.vin)}, manufacturer=vehicle.brand.name, model=vehicle.name, name=f"{vehicle.brand.name} {vehicle.name}", ) - def update_callback(self) -> None: - """Schedule a state update.""" - self.schedule_update_ha_state(True) - async def async_added_to_hass(self) -> None: - """Add callback after being added to hass. - - Show latest data after startup. - """ - self._account.add_update_listener(self.update_callback) + """When entity is added to hass.""" + await super().async_added_to_hass() + self._handle_coordinator_update() diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index 8110b535716..cae70f6de4b 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -20,100 +20,37 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.unit_system import UnitSystem -from . import ( - DOMAIN as BMW_DOMAIN, - BMWConnectedDriveAccount, - BMWConnectedDriveBaseEntity, -) -from .const import CONF_ACCOUNT, DATA_ENTRIES, UNIT_MAP +from . import BMWConnectedDriveBaseEntity +from .const import DOMAIN, UNIT_MAP +from .coordinator import BMWDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -def _are_doors_closed( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class opening: On means open, Off means closed - _LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed) - for lid in vehicle_state.lids: - extra_attributes[lid.name] = lid.state.value - return not vehicle_state.all_lids_closed - - -def _are_windows_closed( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class opening: On means open, Off means closed - for window in vehicle_state.windows: - extra_attributes[window.name] = window.state.value - return not vehicle_state.all_windows_closed - - -def _are_doors_locked( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class lock: On means unlocked, Off means locked - # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - extra_attributes["door_lock_state"] = vehicle_state.door_lock_state.value - extra_attributes["last_update_reason"] = vehicle_state.last_update_reason - return vehicle_state.door_lock_state not in {LockState.LOCKED, LockState.SECURED} - - -def _are_parking_lights_on( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class light: On means light detected, Off means no light - extra_attributes["lights_parking"] = vehicle_state.parking_lights.value - return cast(bool, vehicle_state.are_parking_lights_on) - - -def _are_problems_detected( - vehicle_state: VehicleStatus, - extra_attributes: dict[str, Any], - unit_system: UnitSystem, -) -> bool: - # device class problem: On means problem detected, Off means no problem +def _condition_based_services( + vehicle_state: VehicleStatus, unit_system: UnitSystem +) -> dict[str, Any]: + extra_attributes = {} for report in vehicle_state.condition_based_services: extra_attributes.update(_format_cbs_report(report, unit_system)) - return not vehicle_state.are_all_cbs_ok + return extra_attributes -def _check_control_messages( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class problem: On means problem detected, Off means no problem - check_control_messages = vehicle_state.check_control_messages - has_check_control_messages = vehicle_state.has_check_control_messages - if has_check_control_messages: - cbs_list = [message.description_short for message in check_control_messages] +def _check_control_messages(vehicle_state: VehicleStatus) -> dict[str, Any]: + extra_attributes: dict[str, Any] = {} + if vehicle_state.has_check_control_messages: + cbs_list = [ + message.description_short + for message in vehicle_state.check_control_messages + ] extra_attributes["check_control_messages"] = cbs_list else: extra_attributes["check_control_messages"] = "OK" - return cast(bool, vehicle_state.has_check_control_messages) - - -def _is_vehicle_charging( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class power: On means power detected, Off means no power - extra_attributes["charging_status"] = vehicle_state.charging_status.value - extra_attributes[ - "last_charging_end_result" - ] = vehicle_state.last_charging_end_result - return cast(bool, vehicle_state.charging_status == ChargingState.CHARGING) - - -def _is_vehicle_plugged_in( - vehicle_state: VehicleStatus, extra_attributes: dict[str, Any], *args: Any -) -> bool: - # device class plug: On means device is plugged in, - # Off means device is unplugged - extra_attributes["connection_status"] = vehicle_state.connection_status - return cast(str, vehicle_state.connection_status) == "CONNECTED" + return extra_attributes def _format_cbs_report( @@ -139,7 +76,7 @@ def _format_cbs_report( class BMWRequiredKeysMixin: """Mixin for required keys.""" - value_fn: Callable[[VehicleStatus, dict[str, Any], UnitSystem], bool] + value_fn: Callable[[VehicleStatus], bool] @dataclass @@ -148,6 +85,8 @@ class BMWBinarySensorEntityDescription( ): """Describes BMW binary_sensor entity.""" + attr_fn: Callable[[VehicleStatus, UnitSystem], dict[str, Any]] | None = None + SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( BMWBinarySensorEntityDescription( @@ -155,42 +94,59 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( name="Doors", device_class=BinarySensorDeviceClass.OPENING, icon="mdi:car-door-lock", - value_fn=_are_doors_closed, + # device class opening: On means open, Off means closed + value_fn=lambda s: not s.all_lids_closed, + attr_fn=lambda s, u: {lid.name: lid.state.value for lid in s.lids}, ), BMWBinarySensorEntityDescription( key="windows", name="Windows", device_class=BinarySensorDeviceClass.OPENING, icon="mdi:car-door", - value_fn=_are_windows_closed, + # device class opening: On means open, Off means closed + value_fn=lambda s: not s.all_windows_closed, + attr_fn=lambda s, u: {window.name: window.state.value for window in s.windows}, ), BMWBinarySensorEntityDescription( key="door_lock_state", name="Door lock state", device_class=BinarySensorDeviceClass.LOCK, icon="mdi:car-key", - value_fn=_are_doors_locked, + # device class lock: On means unlocked, Off means locked + # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED + value_fn=lambda s: s.door_lock_state + not in {LockState.LOCKED, LockState.SECURED}, + attr_fn=lambda s, u: { + "door_lock_state": s.door_lock_state.value, + "last_update_reason": s.last_update_reason, + }, ), BMWBinarySensorEntityDescription( key="lights_parking", name="Parking lights", device_class=BinarySensorDeviceClass.LIGHT, icon="mdi:car-parking-lights", - value_fn=_are_parking_lights_on, + # device class light: On means light detected, Off means no light + value_fn=lambda s: cast(bool, s.are_parking_lights_on), + attr_fn=lambda s, u: {"lights_parking": s.parking_lights.value}, ), BMWBinarySensorEntityDescription( key="condition_based_services", name="Condition based services", device_class=BinarySensorDeviceClass.PROBLEM, icon="mdi:wrench", - value_fn=_are_problems_detected, + # device class problem: On means problem detected, Off means no problem + value_fn=lambda s: not s.are_all_cbs_ok, + attr_fn=_condition_based_services, ), BMWBinarySensorEntityDescription( key="check_control_messages", name="Control messages", device_class=BinarySensorDeviceClass.PROBLEM, icon="mdi:car-tire-alert", - value_fn=_check_control_messages, + # device class problem: On means problem detected, Off means no problem + value_fn=lambda s: cast(bool, s.has_check_control_messages), + attr_fn=lambda s, u: _check_control_messages(s), ), # electric BMWBinarySensorEntityDescription( @@ -198,14 +154,20 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = ( name="Charging status", device_class=BinarySensorDeviceClass.BATTERY_CHARGING, icon="mdi:ev-station", - value_fn=_is_vehicle_charging, + # device class power: On means power detected, Off means no power + value_fn=lambda s: cast(bool, s.charging_status == ChargingState.CHARGING), + attr_fn=lambda s, u: { + "charging_status": s.charging_status.value, + "last_charging_end_result": s.last_charging_end_result, + }, ), BMWBinarySensorEntityDescription( key="connection_status", name="Connection status", device_class=BinarySensorDeviceClass.PLUG, icon="mdi:car-electric", - value_fn=_is_vehicle_plugged_in, + value_fn=lambda s: cast(str, s.connection_status) == "CONNECTED", + attr_fn=lambda s, u: {"connection_status": s.connection_status}, ), ) @@ -216,17 +178,15 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the BMW ConnectedDrive binary sensors from config entry.""" - account: BMWConnectedDriveAccount = hass.data[BMW_DOMAIN][DATA_ENTRIES][ - config_entry.entry_id - ][CONF_ACCOUNT] + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities = [ - BMWConnectedDriveSensor(account, vehicle, description, hass.config.units) - for vehicle in account.account.vehicles + BMWConnectedDriveSensor(coordinator, vehicle, description, hass.config.units) + for vehicle in coordinator.account.vehicles for description in SENSOR_TYPES if description.key in vehicle.available_attributes ] - async_add_entities(entities, True) + async_add_entities(entities) class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): @@ -236,26 +196,35 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): def __init__( self, - account: BMWConnectedDriveAccount, + coordinator: BMWDataUpdateCoordinator, vehicle: ConnectedDriveVehicle, description: BMWBinarySensorEntityDescription, unit_system: UnitSystem, ) -> None: """Initialize sensor.""" - super().__init__(account, vehicle) + super().__init__(coordinator, vehicle) self.entity_description = description self._unit_system = unit_system self._attr_name = f"{vehicle.name} {description.key}" self._attr_unique_id = f"{vehicle.vin}-{description.key}" - def update(self) -> None: - """Read new state data from the library.""" - _LOGGER.debug("Updating binary sensors of %s", self._vehicle.name) - vehicle_state = self._vehicle.status - result = self._attrs.copy() - - self._attr_is_on = self.entity_description.value_fn( - vehicle_state, result, self._unit_system + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + _LOGGER.debug( + "Updating binary sensor '%s' of %s", + self.entity_description.key, + self.vehicle.name, ) - self._attr_extra_state_attributes = result + vehicle_state = self.vehicle.status + + self._attr_is_on = self.entity_description.value_fn(vehicle_state) + + if self.entity_description.attr_fn: + self._attr_extra_state_attributes = dict( + self._attrs, + **self.entity_description.attr_fn(vehicle_state, self._unit_system), + ) + + super()._handle_coordinator_update() diff --git a/homeassistant/components/bmw_connected_drive/button.py b/homeassistant/components/bmw_connected_drive/button.py index 72d66d746ab..254fbebfdac 100644 --- a/homeassistant/components/bmw_connected_drive/button.py +++ b/homeassistant/components/bmw_connected_drive/button.py @@ -1,10 +1,11 @@ """Support for BMW connected drive button entities.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine from dataclasses import dataclass +import logging +from typing import TYPE_CHECKING -from bimmer_connected.remote_services import RemoteServiceStatus from bimmer_connected.vehicle import ConnectedDriveVehicle from homeassistant.components.button import ButtonEntity, ButtonEntityDescription @@ -12,12 +13,13 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ( - DOMAIN as BMW_DOMAIN, - BMWConnectedDriveAccount, - BMWConnectedDriveBaseEntity, -) -from .const import CONF_ACCOUNT, DATA_ENTRIES +from . import BMWConnectedDriveBaseEntity +from .const import DOMAIN + +if TYPE_CHECKING: + from .coordinator import BMWDataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) @dataclass @@ -25,10 +27,8 @@ class BMWButtonEntityDescription(ButtonEntityDescription): """Class describing BMW button entities.""" enabled_when_read_only: bool = False - remote_function: Callable[ - [ConnectedDriveVehicle], RemoteServiceStatus - ] | None = None - account_function: Callable[[BMWConnectedDriveAccount], None] | None = None + remote_function: str | None = None + account_function: Callable[[BMWDataUpdateCoordinator], Coroutine] | None = None BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = ( @@ -36,37 +36,37 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = ( key="light_flash", icon="mdi:car-light-alert", name="Flash Lights", - remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_light_flash(), + remote_function="trigger_remote_light_flash", ), BMWButtonEntityDescription( key="sound_horn", icon="mdi:bullhorn", name="Sound Horn", - remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_horn(), + remote_function="trigger_remote_horn", ), BMWButtonEntityDescription( key="activate_air_conditioning", icon="mdi:hvac", name="Activate Air Conditioning", - remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(), + remote_function="trigger_remote_air_conditioning", ), BMWButtonEntityDescription( key="deactivate_air_conditioning", icon="mdi:hvac-off", name="Deactivate Air Conditioning", - remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(), + remote_function="trigger_remote_air_conditioning_stop", ), BMWButtonEntityDescription( key="find_vehicle", icon="mdi:crosshairs-question", name="Find Vehicle", - remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_vehicle_finder(), + remote_function="trigger_remote_vehicle_finder", ), BMWButtonEntityDescription( key="refresh", icon="mdi:refresh", name="Refresh from cloud", - account_function=lambda account: account.update(), + account_function=lambda coordinator: coordinator.async_request_refresh(), enabled_when_read_only=True, ), ) @@ -78,18 +78,17 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the BMW ConnectedDrive buttons from config entry.""" - account: BMWConnectedDriveAccount = hass.data[BMW_DOMAIN][DATA_ENTRIES][ - config_entry.entry_id - ][CONF_ACCOUNT] + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + entities: list[BMWButton] = [] - for vehicle in account.account.vehicles: + for vehicle in coordinator.account.vehicles: entities.extend( [ - BMWButton(account, vehicle, description) + BMWButton(coordinator, vehicle, description) for description in BUTTON_TYPES - if not account.read_only - or (account.read_only and description.enabled_when_read_only) + if not coordinator.read_only + or (coordinator.read_only and description.enabled_when_read_only) ] ) @@ -103,20 +102,35 @@ class BMWButton(BMWConnectedDriveBaseEntity, ButtonEntity): def __init__( self, - account: BMWConnectedDriveAccount, + coordinator: BMWDataUpdateCoordinator, vehicle: ConnectedDriveVehicle, description: BMWButtonEntityDescription, ) -> None: """Initialize BMW vehicle sensor.""" - super().__init__(account, vehicle) + super().__init__(coordinator, vehicle) self.entity_description = description self._attr_name = f"{vehicle.name} {description.name}" self._attr_unique_id = f"{vehicle.vin}-{description.key}" - def press(self) -> None: - """Process the button press.""" + async def async_press(self) -> None: + """Press the button.""" if self.entity_description.remote_function: - self.entity_description.remote_function(self._vehicle) + await self.hass.async_add_executor_job( + getattr( + self.vehicle.remote_services, + self.entity_description.remote_function, + ) + ) elif self.entity_description.account_function: - self.entity_description.account_function(self._account) + _LOGGER.warning( + "The 'Refresh from cloud' button is deprecated. Use the 'homeassistant.update_entity' " + "service with any BMW entity for a full reload. See https://www.home-assistant.io/" + "integrations/bmw_connected_drive/#update-the-state--refresh-from-api for details" + ) + await self.entity_description.account_function(self.coordinator) + + # Always update HA states after a button was executed. + # BMW remote services that change the vehicle's state update the local object + # when executing the service, so only the HA state machine needs further updates. + self.coordinator.notify_listeners() diff --git a/homeassistant/components/bmw_connected_drive/const.py b/homeassistant/components/bmw_connected_drive/const.py index f7908910803..a2082c0bede 100644 --- a/homeassistant/components/bmw_connected_drive/const.py +++ b/homeassistant/components/bmw_connected_drive/const.py @@ -6,17 +6,17 @@ from homeassistant.const import ( VOLUME_LITERS, ) +DOMAIN = "bmw_connected_drive" ATTRIBUTION = "Data provided by BMW Connected Drive" ATTR_DIRECTION = "direction" +ATTR_VIN = "vin" CONF_ALLOWED_REGIONS = ["china", "north_america", "rest_of_world"] CONF_READ_ONLY = "read_only" - CONF_ACCOUNT = "account" DATA_HASS_CONFIG = "hass_config" -DATA_ENTRIES = "entries" UNIT_MAP = { "KILOMETERS": LENGTH_KILOMETERS, diff --git a/homeassistant/components/bmw_connected_drive/coordinator.py b/homeassistant/components/bmw_connected_drive/coordinator.py new file mode 100644 index 00000000000..a02b4bdd27c --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/coordinator.py @@ -0,0 +1,74 @@ +"""Coordinator for BMW.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +import async_timeout +from bimmer_connected.account import ConnectedDriveAccount +from bimmer_connected.country_selector import get_region_from_name + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +SCAN_INTERVAL = timedelta(seconds=300) +_LOGGER = logging.getLogger(__name__) + + +class BMWDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching BMW data.""" + + account: ConnectedDriveAccount + + def __init__( + self, + hass: HomeAssistant, + *, + username: str, + password: str, + region: str, + read_only: bool = False, + ) -> None: + """Initialize account-wide BMW data updater.""" + # Storing username & password in coordinator is needed until a new library version + # that does not do blocking IO on init. + self._username = username + self._password = password + self._region = get_region_from_name(region) + + self.account = None + self.read_only = read_only + + super().__init__( + hass, + _LOGGER, + name=f"{DOMAIN}-{username}", + update_interval=SCAN_INTERVAL, + ) + + async def _async_update_data(self) -> None: + """Fetch data from BMW.""" + try: + async with async_timeout.timeout(15): + if isinstance(self.account, ConnectedDriveAccount): + # pylint: disable=protected-access + await self.hass.async_add_executor_job(self.account._get_vehicles) + else: + self.account = await self.hass.async_add_executor_job( + ConnectedDriveAccount, + self._username, + self._password, + self._region, + ) + self.account.set_observer_position( + self.hass.config.latitude, self.hass.config.longitude + ) + except OSError as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err + + def notify_listeners(self) -> None: + """Notify all listeners to refresh HA state machine.""" + for update_callback in self._listeners: + update_callback() diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 90629216641..b1fa429f5b9 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -12,12 +12,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ( - DOMAIN as BMW_DOMAIN, - BMWConnectedDriveAccount, - BMWConnectedDriveBaseEntity, -) -from .const import ATTR_DIRECTION, CONF_ACCOUNT, DATA_ENTRIES +from . import BMWConnectedDriveBaseEntity +from .const import ATTR_DIRECTION, DOMAIN +from .coordinator import BMWDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -28,20 +25,18 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the BMW ConnectedDrive tracker from config entry.""" - account: BMWConnectedDriveAccount = hass.data[BMW_DOMAIN][DATA_ENTRIES][ - config_entry.entry_id - ][CONF_ACCOUNT] + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[BMWDeviceTracker] = [] - for vehicle in account.account.vehicles: - entities.append(BMWDeviceTracker(account, vehicle)) + for vehicle in coordinator.account.vehicles: + entities.append(BMWDeviceTracker(coordinator, vehicle)) if not vehicle.is_vehicle_tracking_enabled: _LOGGER.info( "Tracking is (currently) disabled for vehicle %s (%s), defaulting to unknown", vehicle.name, vehicle.vin, ) - async_add_entities(entities, True) + async_add_entities(entities) class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): @@ -52,39 +47,39 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): def __init__( self, - account: BMWConnectedDriveAccount, + coordinator: BMWDataUpdateCoordinator, vehicle: ConnectedDriveVehicle, ) -> None: """Initialize the Tracker.""" - super().__init__(account, vehicle) + super().__init__(coordinator, vehicle) self._attr_unique_id = vehicle.vin - self._location = pos if (pos := vehicle.status.gps_position) else None self._attr_name = vehicle.name + @property + def extra_state_attributes(self) -> dict: + """Return entity specific state attributes.""" + return dict(self._attrs, **{ATTR_DIRECTION: self.vehicle.status.gps_heading}) + @property def latitude(self) -> float | None: """Return latitude value of the device.""" - return self._location[0] if self._location else None + return ( + self.vehicle.status.gps_position[0] + if self.vehicle.is_vehicle_tracking_enabled + else None + ) @property def longitude(self) -> float | None: """Return longitude value of the device.""" - return self._location[1] if self._location else None + return ( + self.vehicle.status.gps_position[1] + if self.vehicle.is_vehicle_tracking_enabled + else None + ) @property def source_type(self) -> Literal["gps"]: """Return the source type, eg gps or router, of the device.""" return SOURCE_TYPE_GPS - - def update(self) -> None: - """Update state of the device tracker.""" - _LOGGER.debug("Updating device tracker of %s", self._vehicle.name) - state_attrs = self._attrs - state_attrs[ATTR_DIRECTION] = self._vehicle.status.gps_heading - self._attr_extra_state_attributes = state_attrs - self._location = ( - self._vehicle.status.gps_position - if self._vehicle.is_vehicle_tracking_enabled - else None - ) diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 71539019f82..a395c80ebcc 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -1,4 +1,6 @@ """Support for BMW car locks with BMW ConnectedDrive.""" +from __future__ import annotations + import logging from typing import Any @@ -7,15 +9,12 @@ from bimmer_connected.vehicle_status import LockState from homeassistant.components.lock import LockEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ( - DOMAIN as BMW_DOMAIN, - BMWConnectedDriveAccount, - BMWConnectedDriveBaseEntity, -) -from .const import CONF_ACCOUNT, DATA_ENTRIES +from . import BMWConnectedDriveBaseEntity +from .const import DOMAIN +from .coordinator import BMWDataUpdateCoordinator DOOR_LOCK_STATE = "door_lock_state" _LOGGER = logging.getLogger(__name__) @@ -27,16 +26,14 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the BMW ConnectedDrive binary sensors from config entry.""" - account: BMWConnectedDriveAccount = hass.data[BMW_DOMAIN][DATA_ENTRIES][ - config_entry.entry_id - ][CONF_ACCOUNT] + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - if not account.read_only: - entities = [ - BMWLock(account, vehicle, "lock", "BMW lock") - for vehicle in account.account.vehicles - ] - async_add_entities(entities, True) + entities: list[BMWLock] = [] + + for vehicle in coordinator.account.vehicles: + if not coordinator.read_only: + entities.append(BMWLock(coordinator, vehicle, "lock", "BMW lock")) + async_add_entities(entities) class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): @@ -44,13 +41,13 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): def __init__( self, - account: BMWConnectedDriveAccount, + coordinator: BMWDataUpdateCoordinator, vehicle: ConnectedDriveVehicle, attribute: str, sensor_name: str, ) -> None: """Initialize the lock.""" - super().__init__(account, vehicle) + super().__init__(coordinator, vehicle) self._attribute = attribute self._attr_name = f"{vehicle.name} {attribute}" @@ -60,38 +57,43 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): def lock(self, **kwargs: Any) -> None: """Lock the car.""" - _LOGGER.debug("%s: locking doors", self._vehicle.name) - # Optimistic state set here because it takes some time before the - # update callback response - self._attr_is_locked = True - self.schedule_update_ha_state() - self._vehicle.remote_services.trigger_remote_door_lock() + _LOGGER.debug("%s: locking doors", self.vehicle.name) + # Only update the HA state machine if the vehicle reliably reports its lock state + if self.door_lock_state_available: + # Optimistic state set here because it takes some time before the + # update callback response + self._attr_is_locked = True + self.schedule_update_ha_state() + self.vehicle.remote_services.trigger_remote_door_lock() def unlock(self, **kwargs: Any) -> None: """Unlock the car.""" - _LOGGER.debug("%s: unlocking doors", self._vehicle.name) - # Optimistic state set here because it takes some time before the - # update callback response - self._attr_is_locked = False - self.schedule_update_ha_state() - self._vehicle.remote_services.trigger_remote_door_unlock() + _LOGGER.debug("%s: unlocking doors", self.vehicle.name) + # Only update the HA state machine if the vehicle reliably reports its lock state + if self.door_lock_state_available: + # Optimistic state set here because it takes some time before the + # update callback response + self._attr_is_locked = False + self.schedule_update_ha_state() + self.vehicle.remote_services.trigger_remote_door_unlock() - def update(self) -> None: - """Update state of the lock.""" - _LOGGER.debug( - "Updating lock data for '%s' of %s", self._attribute, self._vehicle.name - ) - vehicle_state = self._vehicle.status - if not self.door_lock_state_available: - self._attr_is_locked = None - else: + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + _LOGGER.debug("Updating lock data of %s", self.vehicle.name) + # Only update the HA state machine if the vehicle reliably reports its lock state + if self.door_lock_state_available: + vehicle_state = self.vehicle.status self._attr_is_locked = vehicle_state.door_lock_state in { LockState.LOCKED, LockState.SECURED, } + self._attr_extra_state_attributes = dict( + self._attrs, + **{ + "door_lock_state": vehicle_state.door_lock_state.value, + "last_update_reason": vehicle_state.last_update_reason, + }, + ) - result = self._attrs.copy() - if self.door_lock_state_available: - result["door_lock_state"] = vehicle_state.door_lock_state.value - result["last_update_reason"] = vehicle_state.last_update_reason - self._attr_extra_state_attributes = result + super()._handle_coordinator_update() diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index ef5a376034a..2d1b5e43984 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.8.11"], + "requirements": ["bimmer_connected==0.8.12"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/components/bmw_connected_drive/notify.py b/homeassistant/components/bmw_connected_drive/notify.py index 2db25aaa592..42e9c834459 100644 --- a/homeassistant/components/bmw_connected_drive/notify.py +++ b/homeassistant/components/bmw_connected_drive/notify.py @@ -11,12 +11,18 @@ from homeassistant.components.notify import ( ATTR_TARGET, BaseNotificationService, ) -from homeassistant.const import ATTR_LATITUDE, ATTR_LOCATION, ATTR_LONGITUDE, ATTR_NAME +from homeassistant.const import ( + ATTR_LATITUDE, + ATTR_LOCATION, + ATTR_LONGITUDE, + ATTR_NAME, + CONF_ENTITY_ID, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import DOMAIN as BMW_DOMAIN, BMWConnectedDriveAccount -from .const import CONF_ACCOUNT, DATA_ENTRIES +from .const import DOMAIN +from .coordinator import BMWDataUpdateCoordinator ATTR_LAT = "lat" ATTR_LOCATION_ATTRIBUTES = ["street", "city", "postal_code", "country"] @@ -33,26 +39,22 @@ def get_service( discovery_info: DiscoveryInfoType | None = None, ) -> BMWNotificationService: """Get the BMW notification service.""" - accounts: list[BMWConnectedDriveAccount] = [ - e[CONF_ACCOUNT] for e in hass.data[BMW_DOMAIN][DATA_ENTRIES].values() + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][ + (discovery_info or {})[CONF_ENTITY_ID] ] - _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) - svc = BMWNotificationService() - svc.setup(accounts) - return svc + + targets = {} + if not coordinator.read_only: + targets.update({v.name: v for v in coordinator.account.vehicles}) + return BMWNotificationService(targets) class BMWNotificationService(BaseNotificationService): """Send Notifications to BMW.""" - def __init__(self) -> None: + def __init__(self, targets: dict[str, ConnectedDriveVehicle]) -> None: """Set up the notification service.""" - self.targets: dict[str, ConnectedDriveVehicle] = {} - - def setup(self, accounts: list[BMWConnectedDriveAccount]) -> None: - """Get the BMW vehicle(s) for the account(s).""" - for account in accounts: - self.targets.update({v.name: v for v in account.account.vehicles}) + self.targets: dict[str, ConnectedDriveVehicle] = targets def send_message(self, message: str = "", **kwargs: Any) -> None: """Send a message or POI to the car.""" diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 8b21a10a39f..4a928870eb2 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -22,17 +22,14 @@ from homeassistant.const import ( VOLUME_GALLONS, VOLUME_LITERS, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util.unit_system import UnitSystem -from . import ( - DOMAIN as BMW_DOMAIN, - BMWConnectedDriveAccount, - BMWConnectedDriveBaseEntity, -) -from .const import CONF_ACCOUNT, DATA_ENTRIES, UNIT_MAP +from . import BMWConnectedDriveBaseEntity +from .const import DOMAIN, UNIT_MAP +from .coordinator import BMWDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -135,21 +132,20 @@ async def async_setup_entry( ) -> None: """Set up the BMW ConnectedDrive sensors from config entry.""" unit_system = hass.config.units - account: BMWConnectedDriveAccount = hass.data[BMW_DOMAIN][DATA_ENTRIES][ - config_entry.entry_id - ][CONF_ACCOUNT] + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + entities: list[BMWConnectedDriveSensor] = [] - for vehicle in account.account.vehicles: + for vehicle in coordinator.account.vehicles: entities.extend( [ - BMWConnectedDriveSensor(account, vehicle, description, unit_system) + BMWConnectedDriveSensor(coordinator, vehicle, description, unit_system) for attribute_name in vehicle.available_attributes if (description := SENSOR_TYPES.get(attribute_name)) ] ) - async_add_entities(entities, True) + async_add_entities(entities) class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): @@ -159,13 +155,13 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): def __init__( self, - account: BMWConnectedDriveAccount, + coordinator: BMWDataUpdateCoordinator, vehicle: ConnectedDriveVehicle, description: BMWSensorEntityDescription, unit_system: UnitSystem, ) -> None: """Initialize BMW vehicle sensor.""" - super().__init__(account, vehicle) + super().__init__(coordinator, vehicle) self.entity_description = description self._attr_name = f"{vehicle.name} {description.key}" @@ -176,8 +172,14 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): else: self._attr_native_unit_of_measurement = description.unit_metric - @property - def native_value(self) -> StateType: - """Return the state.""" - state = getattr(self._vehicle.status, self.entity_description.key) - return cast(StateType, self.entity_description.value(state, self.hass)) + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + _LOGGER.debug( + "Updating sensor '%s' of %s", self.entity_description.key, self.vehicle.name + ) + state = getattr(self.vehicle.status, self.entity_description.key) + self._attr_native_value = cast( + StateType, self.entity_description.value(state, self.hass) + ) + super()._handle_coordinator_update() diff --git a/homeassistant/components/bmw_connected_drive/services.yaml b/homeassistant/components/bmw_connected_drive/services.yaml deleted file mode 100644 index 3f5ff76bdd3..00000000000 --- a/homeassistant/components/bmw_connected_drive/services.yaml +++ /dev/null @@ -1,119 +0,0 @@ -# Describes the format for available services for bmw_connected_drive -# -# The services related to locking/unlocking are implemented in the lock -# component to avoid redundancy. - -light_flash: - name: Flash lights - description: > - Flash the lights of the vehicle. The vehicle is identified either via its - device entry or the VIN. If a VIN is specified, the device entry will be ignored. - fields: - device_id: - name: Car - description: The BMW Connected Drive device - selector: - device: - integration: bmw_connected_drive - vin: - name: VIN - description: The vehicle identification number (VIN) of the vehicle, 17 characters - advanced: true - required: false - example: WBANXXXXXX1234567 - selector: - text: - -sound_horn: - name: Sound horn - description: > - Sound the horn of the vehicle. The vehicle is identified either via its - device entry or the VIN. If a VIN is specified, the device entry will be ignored. - fields: - device_id: - name: Car - description: The BMW Connected Drive device - selector: - device: - integration: bmw_connected_drive - vin: - name: VIN - description: The vehicle identification number (VIN) of the vehicle, 17 characters - advanced: true - required: false - example: WBANXXXXXX1234567 - selector: - text: - -activate_air_conditioning: - name: Activate air conditioning - description: > - Start the air conditioning of the vehicle. What exactly is started here - depends on the type of vehicle. It might range from just ventilation over - auxiliary heating to real air conditioning. The vehicle is identified either via its - device entry or the VIN. If a VIN is specified, the device entry will be ignored. - fields: - device_id: - name: Car - description: The BMW Connected Drive device - selector: - device: - integration: bmw_connected_drive - vin: - name: VIN - description: The vehicle identification number (VIN) of the vehicle, 17 characters - advanced: true - required: false - example: WBANXXXXXX1234567 - selector: - text: - -deactivate_air_conditioning: - name: Deactivate air conditioning - description: > - Stops the air conditioning of the vehicle. This only works on newer vehicles if you also - have the option in the MyBMW app. The vehicle is identified either via its - device entry or the VIN. If a VIN is specified, the device entry will be ignored. - fields: - device_id: - name: Car - description: The BMW Connected Drive device - selector: - device: - integration: bmw_connected_drive - vin: - name: VIN - description: The vehicle identification number (VIN) of the vehicle, 17 characters - advanced: true - required: false - example: WBANXXXXXX1234567 - selector: - text: - -find_vehicle: - name: Find vehicle - description: > - Request vehicle to update the GPS location. The vehicle is identified either via its - device entry or the VIN. If a VIN is specified, the device entry will be ignored. - fields: - device_id: - name: Car - description: The BMW Connected Drive device - selector: - device: - integration: bmw_connected_drive - vin: - name: VIN - description: The vehicle identification number (VIN) of the vehicle, 17 characters - advanced: true - required: false - example: WBANXXXXXX1234567 - selector: - text: - -update_state: - name: Update state - description: > - Fetch the last state of the vehicles of all your accounts from the BMW - server. This does *not* trigger an update from the vehicle, it just gets - the data from the BMW servers. This service does not require any attributes. diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 57be80c0fd7..664431a3145 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -6,14 +6,9 @@ from typing import Any from bond_api import Action, BPUPSubscriptions, DeviceType from homeassistant.components.cover import ( - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -56,18 +51,18 @@ class BondCover(BondEntity, CoverEntity): super().__init__(hub, device, bpup_subs) supported_features = 0 if self._device.supports_open(): - supported_features |= SUPPORT_OPEN + supported_features |= CoverEntityFeature.OPEN if self._device.supports_close(): - supported_features |= SUPPORT_CLOSE + supported_features |= CoverEntityFeature.CLOSE if self._device.supports_tilt_open(): - supported_features |= SUPPORT_OPEN_TILT + supported_features |= CoverEntityFeature.OPEN_TILT if self._device.supports_tilt_close(): - supported_features |= SUPPORT_CLOSE_TILT + supported_features |= CoverEntityFeature.CLOSE_TILT if self._device.supports_hold(): if self._device.supports_open() or self._device.supports_close(): - supported_features |= SUPPORT_STOP + supported_features |= CoverEntityFeature.STOP if self._device.supports_tilt_open() or self._device.supports_tilt_close(): - supported_features |= SUPPORT_STOP_TILT + supported_features |= CoverEntityFeature.STOP_TILT self._attr_supported_features = supported_features def _apply_state(self, state: dict) -> None: diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index a2e35456ca3..9acc7874657 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -12,9 +12,8 @@ import voluptuous as vol from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, - SUPPORT_DIRECTION, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -90,9 +89,9 @@ class BondFan(BondEntity, FanEntity): """Flag supported features.""" features = 0 if self._device.supports_speed(): - features |= SUPPORT_SET_SPEED + features |= FanEntityFeature.SET_SPEED if self._device.supports_direction(): - features |= SUPPORT_DIRECTION + features |= FanEntityFeature.DIRECTION return features diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index b5728ec47d4..c0c3fc428b8 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -8,11 +8,7 @@ from aiohttp.client_exceptions import ClientResponseError from bond_api import Action, BPUPSubscriptions, DeviceType import voluptuous as vol -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -126,7 +122,8 @@ async def async_setup_entry( class BondBaseLight(BondEntity, LightEntity): """Representation of a Bond light.""" - _attr_supported_features = 0 + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} async def async_set_brightness_belief(self, brightness: int) -> None: """Set the belief state of the light.""" @@ -170,7 +167,8 @@ class BondLight(BondBaseLight, BondEntity, LightEntity): """Create HA entity representing Bond light.""" super().__init__(hub, device, bpup_subs, sub_device) if device.supports_set_brightness(): - self._attr_supported_features = SUPPORT_BRIGHTNESS + self._attr_color_mode = ColorMode.BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} def _apply_state(self, state: dict) -> None: self._attr_is_on = state.get("light") == 1 @@ -267,7 +265,8 @@ class BondUpLight(BondBaseLight, BondEntity, LightEntity): class BondFireplace(BondEntity, LightEntity): """Representation of a Bond-controlled fireplace.""" - _attr_supported_features = SUPPORT_BRIGHTNESS + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} def _apply_state(self, state: dict) -> None: power = state.get("power") diff --git a/homeassistant/components/bosch_shc/cover.py b/homeassistant/components/bosch_shc/cover.py index 6048cd51631..fd191d59bc3 100644 --- a/homeassistant/components/bosch_shc/cover.py +++ b/homeassistant/components/bosch_shc/cover.py @@ -3,12 +3,9 @@ from boschshcpy import SHCSession, SHCShutterControl from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -46,7 +43,10 @@ class ShutterControlCover(SHCEntity, CoverEntity): _attr_device_class = CoverDeviceClass.SHUTTER _attr_supported_features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION ) @property diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index 99a2e5a1cb1..745325a4c39 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -1,24 +1,10 @@ """Support for interface with a Bravia TV.""" from __future__ import annotations -from typing import Final - from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, -) -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING @@ -30,20 +16,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import BraviaTVCoordinator from .const import ATTR_MANUFACTURER, DEFAULT_NAME, DOMAIN -SUPPORT_BRAVIA: Final = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY - | SUPPORT_STOP -) - async def async_setup_entry( hass: HomeAssistant, @@ -71,7 +43,19 @@ class BraviaTVMediaPlayer(CoordinatorEntity[BraviaTVCoordinator], MediaPlayerEnt """Representation of a Bravia TV Media Player.""" _attr_device_class = MediaPlayerDeviceClass.TV - _attr_supported_features = SUPPORT_BRAVIA + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + ) def __init__( self, diff --git a/homeassistant/components/braviatv/translations/ca.json b/homeassistant/components/braviatv/translations/ca.json index 94fe36dcddc..b8616f5e8ba 100644 --- a/homeassistant/components/braviatv/translations/ca.json +++ b/homeassistant/components/braviatv/translations/ca.json @@ -21,7 +21,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Configura la integraci\u00f3 de televisor Sony Bravia. Si tens problemes durant la configuraci\u00f3, v\u00e9s a: https://www.home-assistant.io/integrations/braviatv\n\nAssegura't que el televisor estigui engegat.", + "description": "Assegura't que el televisor est\u00e0 engegat abans de configurar-lo.", "title": "Televisor Sony Bravia" } } diff --git a/homeassistant/components/braviatv/translations/de.json b/homeassistant/components/braviatv/translations/de.json index cca5c5aa47f..55ddbec464e 100644 --- a/homeassistant/components/braviatv/translations/de.json +++ b/homeassistant/components/braviatv/translations/de.json @@ -21,7 +21,7 @@ "data": { "host": "Host" }, - "description": "Richte die Sony Bravia TV-Integration ein. Wenn du Probleme mit der Konfiguration hast, gehe zu: https://www.home-assistant.io/integrations/braviatv \n\nStelle sicher, dass dein Fernseher eingeschaltet ist.", + "description": "Stelle sicher, dass dein Fernseher eingeschaltet ist, bevor du versuchst, ihn einzurichten.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/en.json b/homeassistant/components/braviatv/translations/en.json index 05ced6af856..9cc2ac23d17 100644 --- a/homeassistant/components/braviatv/translations/en.json +++ b/homeassistant/components/braviatv/translations/en.json @@ -21,7 +21,7 @@ "data": { "host": "Host" }, - "description": "Set up Sony Bravia TV integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/braviatv \n\nEnsure that your TV is turned on.", + "description": "Ensure that your TV is turned on before trying to set it up.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/et.json b/homeassistant/components/braviatv/translations/et.json index 6930186aeba..c63e3635185 100644 --- a/homeassistant/components/braviatv/translations/et.json +++ b/homeassistant/components/braviatv/translations/et.json @@ -21,7 +21,7 @@ "data": { "host": "" }, - "description": "Seadista Sony Bravia TV sidumine. Kuion probleeme seadetega mine: https://www.home-assistant.io/integrations/braviatv \n\nVeendu, et teler on sisse l\u00fclitatud.", + "description": "Enne teleri seadistamist veendu, et see oleks sisse l\u00fclitatud.", "title": "" } } diff --git a/homeassistant/components/braviatv/translations/fr.json b/homeassistant/components/braviatv/translations/fr.json index d609f1a2fa1..f85aea705fb 100644 --- a/homeassistant/components/braviatv/translations/fr.json +++ b/homeassistant/components/braviatv/translations/fr.json @@ -21,7 +21,7 @@ "data": { "host": "H\u00f4te" }, - "description": "Configurez l'int\u00e9gration du t\u00e9l\u00e9viseur Sony Bravia. Si vous rencontrez des probl\u00e8mes de configuration, rendez-vous sur: https://www.home-assistant.io/integrations/braviatv \n\n Assurez-vous que votre t\u00e9l\u00e9viseur est allum\u00e9.", + "description": "Assurez-vous que votre t\u00e9l\u00e9viseur est allum\u00e9 avant d'essayer de le configurer.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/hu.json b/homeassistant/components/braviatv/translations/hu.json index 00e88955c81..554e74c52b1 100644 --- a/homeassistant/components/braviatv/translations/hu.json +++ b/homeassistant/components/braviatv/translations/hu.json @@ -21,7 +21,7 @@ "data": { "host": "C\u00edm" }, - "description": "\u00c1ll\u00edtsa be a Sony Bravia TV integr\u00e1ci\u00f3t. Ha probl\u00e9m\u00e1i vannak a konfigur\u00e1ci\u00f3val, l\u00e1togasson el a k\u00f6vetkez\u0151 oldalra: https://www.home-assistant.io/integrations/braviatv \n\n Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a TV be van kapcsolva.", + "description": "Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a TV k\u00e9sz\u00fcl\u00e9k be van kapcsolva a be\u00e1ll\u00edt\u00e1s el\u0151tt.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/id.json b/homeassistant/components/braviatv/translations/id.json index def84dacdbb..cb7d5396f7a 100644 --- a/homeassistant/components/braviatv/translations/id.json +++ b/homeassistant/components/braviatv/translations/id.json @@ -21,7 +21,7 @@ "data": { "host": "Host" }, - "description": "Siapkan integrasi TV Sony Bravia. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/braviatv \n\nPastikan TV Anda dinyalakan.", + "description": "Pastikan TV Anda dinyalakan sebelum menyiapkan.", "title": "TV Sony Bravia" } } diff --git a/homeassistant/components/braviatv/translations/it.json b/homeassistant/components/braviatv/translations/it.json index bbd02157496..7ab149fa6c7 100644 --- a/homeassistant/components/braviatv/translations/it.json +++ b/homeassistant/components/braviatv/translations/it.json @@ -21,7 +21,7 @@ "data": { "host": "Host" }, - "description": "Configura l'integrazione TV di Sony Bravia. In caso di problemi con la configurazione visita: https://www.home-assistant.io/integrations/braviatv\n\nAssicurati che il televisore sia acceso.", + "description": "Assicurati che la tua TV sia accesa prima di provare a configurarla.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json index 5354f5761ec..133c5277045 100644 --- a/homeassistant/components/braviatv/translations/nl.json +++ b/homeassistant/components/braviatv/translations/nl.json @@ -21,7 +21,7 @@ "data": { "host": "Host" }, - "description": "Stel Sony Bravia TV-integratie in. Als je problemen hebt met de configuratie ga dan naar: https://www.home-assistant.io/integrations/braviatv \n\nZorg ervoor dat uw tv is ingeschakeld.", + "description": "Zorg ervoor dat uw TV aan staat voordat u hem probeert in te stellen.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/no.json b/homeassistant/components/braviatv/translations/no.json index 0c960a850e2..6465db8d3c0 100644 --- a/homeassistant/components/braviatv/translations/no.json +++ b/homeassistant/components/braviatv/translations/no.json @@ -21,7 +21,7 @@ "data": { "host": "Vert" }, - "description": "Sett opp Sony Bravia TV-integrasjon. Hvis du har problemer med konfigurasjonen, g\u00e5 til: [https://www.home-assistant.io/integrations/braviatv](https://www.home-assistant.io/integrations/braviatv)\n\n Forsikre deg om at TV-en er sl\u00e5tt p\u00e5.", + "description": "S\u00f8rg for at TV-en er sl\u00e5tt p\u00e5 f\u00f8r du pr\u00f8ver \u00e5 sette den opp.", "title": "" } } diff --git a/homeassistant/components/braviatv/translations/pl.json b/homeassistant/components/braviatv/translations/pl.json index 1aa5d7cb58a..354962a62d9 100644 --- a/homeassistant/components/braviatv/translations/pl.json +++ b/homeassistant/components/braviatv/translations/pl.json @@ -21,7 +21,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Konfiguracja integracji telewizora Sony Bravia. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a do strony: https://www.home-assistant.io/integrations/braviatv\n\nUpewnij si\u0119, \u017ce telewizor jest w\u0142\u0105czony.", + "description": "Upewnij si\u0119, \u017ce telewizor jest w\u0142\u0105czony, zanim spr\u00f3bujesz go skonfigurowa\u0107.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/pt-BR.json b/homeassistant/components/braviatv/translations/pt-BR.json index bd6d47af018..a784e30d84c 100644 --- a/homeassistant/components/braviatv/translations/pt-BR.json +++ b/homeassistant/components/braviatv/translations/pt-BR.json @@ -21,7 +21,7 @@ "data": { "host": "Nome do host" }, - "description": "Configure a integra\u00e7\u00e3o do Sony Bravia TV. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/braviatv \n\n Verifique se a sua TV est\u00e1 ligada.", + "description": "Certifique-se de que sua TV esteja ligada antes de tentar configur\u00e1-la.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/ru.json b/homeassistant/components/braviatv/translations/ru.json index add32dd0d79..3aeb83f7686 100644 --- a/homeassistant/components/braviatv/translations/ru.json +++ b/homeassistant/components/braviatv/translations/ru.json @@ -21,7 +21,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439 \u043f\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438:\nhttps://www.home-assistant.io/integrations/braviatv", + "description": "\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u0430\u0448 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d.", "title": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 Sony Bravia" } } diff --git a/homeassistant/components/braviatv/translations/tr.json b/homeassistant/components/braviatv/translations/tr.json index 6d0f82e29a4..b6df4a7999a 100644 --- a/homeassistant/components/braviatv/translations/tr.json +++ b/homeassistant/components/braviatv/translations/tr.json @@ -21,7 +21,7 @@ "data": { "host": "Ana Bilgisayar" }, - "description": "Sony Bravia TV entegrasyonunu ayarlay\u0131n. Yap\u0131land\u0131rmayla ilgili sorunlar\u0131n\u0131z varsa \u015fu adrese gidin: https://www.home-assistant.io/integrations/braviatv \n\n TV'nizin a\u00e7\u0131k oldu\u011fundan emin olun.", + "description": "Kurmaya \u00e7al\u0131\u015fmadan \u00f6nce TV'nizin a\u00e7\u0131k oldu\u011fundan emin olun.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/zh-Hant.json b/homeassistant/components/braviatv/translations/zh-Hant.json index f736b601a74..35ef6ef2e4f 100644 --- a/homeassistant/components/braviatv/translations/zh-Hant.json +++ b/homeassistant/components/braviatv/translations/zh-Hant.json @@ -21,7 +21,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8a2d\u5b9a Sony Bravia \u96fb\u8996\u6574\u5408\u3002\u5047\u5982\u65bc\u8a2d\u5b9a\u904e\u7a0b\u4e2d\u906d\u9047\u56f0\u7136\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/braviatv \n\n\u78ba\u5b9a\u96fb\u8996\u5df2\u7d93\u958b\u555f\u3002", + "description": "\u65bc\u8a2d\u5b9a\u524d\u78ba\u5b9a\u96fb\u8996\u5df2\u7d93\u958b\u555f\u3002", "title": "Sony Bravia \u96fb\u8996" } } diff --git a/homeassistant/components/broadlink/light.py b/homeassistant/components/broadlink/light.py index 9af50a345fb..4b655e4bd42 100644 --- a/homeassistant/components/broadlink/light.py +++ b/homeassistant/components/broadlink/light.py @@ -8,10 +8,7 @@ from homeassistant.components.light import ( ATTR_COLOR_MODE, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_UNKNOWN, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -56,11 +53,11 @@ class BroadlinkLight(BroadlinkEntity, LightEntity): data = self._coordinator.data if {"hue", "saturation"}.issubset(data): - self._attr_supported_color_modes.add(COLOR_MODE_HS) + self._attr_supported_color_modes.add(ColorMode.HS) if "colortemp" in data: - self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) if not self.supported_color_modes: - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} self._update_state(data) @@ -72,8 +69,8 @@ class BroadlinkLight(BroadlinkEntity, LightEntity): if "brightness" in data: self._attr_brightness = round(data["brightness"] * 2.55) - if self.supported_color_modes == {COLOR_MODE_BRIGHTNESS}: - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + if self.supported_color_modes == {ColorMode.BRIGHTNESS}: + self._attr_color_mode = ColorMode.BRIGHTNESS return if {"hue", "saturation"}.issubset(data): @@ -84,12 +81,12 @@ class BroadlinkLight(BroadlinkEntity, LightEntity): if "bulb_colormode" in data: if data["bulb_colormode"] == BROADLINK_COLOR_MODE_RGB: - self._attr_color_mode = COLOR_MODE_HS + self._attr_color_mode = ColorMode.HS elif data["bulb_colormode"] == BROADLINK_COLOR_MODE_WHITE: - self._attr_color_mode = COLOR_MODE_COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP else: # Scenes are not yet supported. - self._attr_color_mode = COLOR_MODE_UNKNOWN + self._attr_color_mode = ColorMode.UNKNOWN async def async_turn_on(self, **kwargs): """Turn on the light.""" @@ -99,7 +96,7 @@ class BroadlinkLight(BroadlinkEntity, LightEntity): brightness = kwargs[ATTR_BRIGHTNESS] state["brightness"] = round(brightness / 2.55) - if self.supported_color_modes == {COLOR_MODE_BRIGHTNESS}: + if self.supported_color_modes == {ColorMode.BRIGHTNESS}: state["bulb_colormode"] = BROADLINK_COLOR_MODE_WHITE elif ATTR_HS_COLOR in kwargs: @@ -115,9 +112,9 @@ class BroadlinkLight(BroadlinkEntity, LightEntity): elif ATTR_COLOR_MODE in kwargs: color_mode = kwargs[ATTR_COLOR_MODE] - if color_mode == COLOR_MODE_HS: + if color_mode == ColorMode.HS: state["bulb_colormode"] = BROADLINK_COLOR_MODE_RGB - elif color_mode == COLOR_MODE_COLOR_TEMP: + elif color_mode == ColorMode.COLOR_TEMP: state["bulb_colormode"] = BROADLINK_COLOR_MODE_WHITE else: # Scenes are not yet supported. diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index b7d81e5ba42..dd0f40d45bc 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -27,9 +27,8 @@ from homeassistant.components.remote import ( SERVICE_DELETE_COMMAND, SERVICE_LEARN_COMMAND, SERVICE_SEND_COMMAND, - SUPPORT_DELETE_COMMAND, - SUPPORT_LEARN_COMMAND, RemoteEntity, + RemoteEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_COMMAND, STATE_OFF @@ -117,7 +116,9 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity): self._attr_name = f"{device.name} Remote" self._attr_is_on = True - self._attr_supported_features = SUPPORT_LEARN_COMMAND | SUPPORT_DELETE_COMMAND + self._attr_supported_features = ( + RemoteEntityFeature.LEARN_COMMAND | RemoteEntityFeature.DELETE_COMMAND + ) self._attr_unique_id = device.unique_id def _extract_codes(self, commands, device=None): diff --git a/homeassistant/components/broadlink/translations/hu.json b/homeassistant/components/broadlink/translations/hu.json index 0bab0c1752f..70fd71b5897 100644 --- a/homeassistant/components/broadlink/translations/hu.json +++ b/homeassistant/components/broadlink/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", "not_supported": "Az eszk\u00f6z nem t\u00e1mogatott", @@ -20,7 +20,7 @@ }, "finish": { "data": { - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "title": "V\u00e1lasszonegy nevet az eszk\u00f6znek" }, diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index aaf1af72db9..c230373ea36 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -3,7 +3,7 @@ "name": "Brother Printer", "documentation": "https://www.home-assistant.io/integrations/brother", "codeowners": ["@bieniu"], - "requirements": ["brother==1.1.0"], + "requirements": ["brother==1.2.0"], "zeroconf": [ { "type": "_printer._tcp.local.", diff --git a/homeassistant/components/brother/translations/ca.json b/homeassistant/components/brother/translations/ca.json index 689495478bb..d9b664dfe82 100644 --- a/homeassistant/components/brother/translations/ca.json +++ b/homeassistant/components/brother/translations/ca.json @@ -15,14 +15,13 @@ "data": { "host": "Amfitri\u00f3", "type": "Tipus d'impressora" - }, - "description": "Configura la integraci\u00f3 d'impressora Brother. Si tens problemes amb la configuraci\u00f3, visita: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Tipus d'impressora" }, - "description": "Vols afegir la impressora Brother {model} amb n\u00famero de s\u00e8rie `{serial_number}` a Home Assistant?", + "description": "Vols afegir la impressora {model} amb n\u00famero de s\u00e8rie `{serial_number}` a Home Assistant?", "title": "Impressora Brother descoberta" } } diff --git a/homeassistant/components/brother/translations/da.json b/homeassistant/components/brother/translations/da.json index fc9cd0079d8..7dcd8188085 100644 --- a/homeassistant/components/brother/translations/da.json +++ b/homeassistant/components/brother/translations/da.json @@ -14,8 +14,7 @@ "data": { "host": "Printerens v\u00e6rtsnavn eller IP-adresse", "type": "Type af printer" - }, - "description": "Konfigurer Brother-printerintegration. Hvis du har problemer med konfiguration, kan du g\u00e5 til: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/de.json b/homeassistant/components/brother/translations/de.json index 8126a04f21d..e8056b0505a 100644 --- a/homeassistant/components/brother/translations/de.json +++ b/homeassistant/components/brother/translations/de.json @@ -15,14 +15,13 @@ "data": { "host": "Host", "type": "Typ des Druckers" - }, - "description": "Einrichten der Brother-Drucker-Integration. Wenn Du Probleme mit der Konfiguration hast, gehe zu: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Typ des Druckers" }, - "description": "M\u00f6chtest du den Brother Drucker {model} mit der Seriennummer `{serial_number}` zum Home Assistant hinzuf\u00fcgen?", + "description": "M\u00f6chtest du den Drucker {model} mit der Seriennummer ` {serial_number} ` zu Home Assistant hinzuf\u00fcgen?", "title": "Brother-Drucker entdeckt" } } diff --git a/homeassistant/components/brother/translations/el.json b/homeassistant/components/brother/translations/el.json index f8a27353a6a..e8c9e4ea8e7 100644 --- a/homeassistant/components/brother/translations/el.json +++ b/homeassistant/components/brother/translations/el.json @@ -15,8 +15,7 @@ "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c4\u03bf\u03c5 \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae" - }, - "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b5\u03ba\u03c4\u03c5\u03c0\u03c9\u03c4\u03ae Brother. \u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/en.json b/homeassistant/components/brother/translations/en.json index 8ec9d84c7fb..997cdb1ea83 100644 --- a/homeassistant/components/brother/translations/en.json +++ b/homeassistant/components/brother/translations/en.json @@ -15,14 +15,13 @@ "data": { "host": "Host", "type": "Type of the printer" - }, - "description": "Set up Brother printer integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Type of the printer" }, - "description": "Do you want to add the Brother Printer {model} with serial number `{serial_number}` to Home Assistant?", + "description": "Do you want to add the printer {model} with serial number `{serial_number}` to Home Assistant?", "title": "Discovered Brother Printer" } } diff --git a/homeassistant/components/brother/translations/es-419.json b/homeassistant/components/brother/translations/es-419.json index 33d06705017..21c57cab021 100644 --- a/homeassistant/components/brother/translations/es-419.json +++ b/homeassistant/components/brother/translations/es-419.json @@ -14,8 +14,7 @@ "data": { "host": "Nombre de host de la impresora o direcci\u00f3n IP", "type": "Tipo de impresora" - }, - "description": "Configure la integraci\u00f3n de la impresora Brother. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/es.json b/homeassistant/components/brother/translations/es.json index 43b3cefdded..bc6aa749445 100644 --- a/homeassistant/components/brother/translations/es.json +++ b/homeassistant/components/brother/translations/es.json @@ -15,8 +15,7 @@ "data": { "host": "Host", "type": "Tipo de impresora" - }, - "description": "Configura la integraci\u00f3n de impresoras Brother. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/et.json b/homeassistant/components/brother/translations/et.json index 1115e5cb3ca..99928a5d1ab 100644 --- a/homeassistant/components/brother/translations/et.json +++ b/homeassistant/components/brother/translations/et.json @@ -15,14 +15,13 @@ "data": { "host": "Host", "type": "Printeri t\u00fc\u00fcp" - }, - "description": "Seadista Brotheri printeri sidumine. Kui seadistamisega on probleeme mine aadressile https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Printeri t\u00fc\u00fcp" }, - "description": "Kas soovite lisada Home Assistanti Brotheri printeri {model} seerianumbriga \" {serial_number} \"?", + "description": "Kas lisada Home Assistantile printer {model} seerianumbriga ` {serial_number} `?", "title": "Avastatud Brotheri printer" } } diff --git a/homeassistant/components/brother/translations/fr.json b/homeassistant/components/brother/translations/fr.json index 851d46f1772..4d248d05102 100644 --- a/homeassistant/components/brother/translations/fr.json +++ b/homeassistant/components/brother/translations/fr.json @@ -15,14 +15,13 @@ "data": { "host": "H\u00f4te", "type": "Type d'imprimante" - }, - "description": "Configurez l'int\u00e9gration de l'imprimante Brother. Si vous avez des probl\u00e8mes avec la configuration, allez \u00e0 : https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Type d'imprimante" }, - "description": "Voulez-vous ajouter l'imprimante Brother {model} avec le num\u00e9ro de s\u00e9rie `{serial_number}` \u00e0 Home Assistant ?", + "description": "Voulez-vous ajouter l'imprimante {model} portant le num\u00e9ro de s\u00e9rie `{serial_number}` \u00e0 Home Assistant\u00a0?", "title": "Imprimante Brother d\u00e9couverte" } } diff --git a/homeassistant/components/brother/translations/hu.json b/homeassistant/components/brother/translations/hu.json index f0218dc2647..93c368cf74a 100644 --- a/homeassistant/components/brother/translations/hu.json +++ b/homeassistant/components/brother/translations/hu.json @@ -15,14 +15,13 @@ "data": { "host": "C\u00edm", "type": "A nyomtat\u00f3 t\u00edpusa" - }, - "description": "A Brother nyomtat\u00f3 integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha probl\u00e9m\u00e1id vannak a konfigur\u00e1ci\u00f3val, l\u00e1togass el a k\u00f6vetkez\u0151 oldalra: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "A nyomtat\u00f3 t\u00edpusa" }, - "description": "Hozz\u00e1 szeretn\u00e9 adni a {model} Brother nyomtat\u00f3t, amelynek sorsz\u00e1ma: `{serial_number}`, Home Assistanthoz?", + "description": "Hozz\u00e1 szeretn\u00e9 adni a {model} nyomtat\u00f3t, amelynek sorsz\u00e1ma: `{serial_number}`, Home Assistanthoz?", "title": "Felfedezett Brother nyomtat\u00f3" } } diff --git a/homeassistant/components/brother/translations/id.json b/homeassistant/components/brother/translations/id.json index ed02999710e..135274450d3 100644 --- a/homeassistant/components/brother/translations/id.json +++ b/homeassistant/components/brother/translations/id.json @@ -15,14 +15,13 @@ "data": { "host": "Host", "type": "Jenis printer" - }, - "description": "Siapkan integrasi printer Brother. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Jenis printer" }, - "description": "Ingin menambahkan Printer Brother {model} dengan nomor seri `{serial_number}` ke Home Assistant?", + "description": "Ingin menambahkan printer {model} dengan nomor seri `{serial_number}` ke Home Assistant?", "title": "Perangkat Printer Brother yang Ditemukan" } } diff --git a/homeassistant/components/brother/translations/it.json b/homeassistant/components/brother/translations/it.json index 29059723159..21fd1bf15ab 100644 --- a/homeassistant/components/brother/translations/it.json +++ b/homeassistant/components/brother/translations/it.json @@ -15,14 +15,13 @@ "data": { "host": "Host", "type": "Tipo di stampante" - }, - "description": "Configura l'integrazione della stampante Brother. In caso di problemi con la configurazione, visita: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Tipo di stampante" }, - "description": "Vuoi aggiungere la stampante Brother {model} con il numero seriale `{serial_number}` a Home Assistant?", + "description": "Vuoi aggiungere la stampante {model} con il numero seriale `{serial_number}` a Home Assistant?", "title": "Trovata stampante Brother" } } diff --git a/homeassistant/components/brother/translations/ja.json b/homeassistant/components/brother/translations/ja.json index 6c0e1e57767..ce3341ef676 100644 --- a/homeassistant/components/brother/translations/ja.json +++ b/homeassistant/components/brother/translations/ja.json @@ -15,8 +15,7 @@ "data": { "host": "\u30db\u30b9\u30c8", "type": "\u30d7\u30ea\u30f3\u30bf\u30fc\u306e\u7a2e\u985e" - }, - "description": "\u30d6\u30e9\u30b6\u30fc\u793e\u88fd\u30d7\u30ea\u30f3\u30bf\u30fc\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002\u8a2d\u5b9a\u306b\u95a2\u3057\u3066\u554f\u984c\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u306f\u3001https://www.home-assistant.io/integrations/brother \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/ko.json b/homeassistant/components/brother/translations/ko.json index 2d3b587e475..a69c415f061 100644 --- a/homeassistant/components/brother/translations/ko.json +++ b/homeassistant/components/brother/translations/ko.json @@ -15,8 +15,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "type": "\ud504\ub9b0\ud130\uc758 \uc885\ub958" - }, - "description": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00\uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/brother \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/lb.json b/homeassistant/components/brother/translations/lb.json index 91964ec90ca..0a0d3785274 100644 --- a/homeassistant/components/brother/translations/lb.json +++ b/homeassistant/components/brother/translations/lb.json @@ -15,8 +15,7 @@ "data": { "host": "Host", "type": "Typ vum Printer" - }, - "description": "Brother Printer Integratioun ariichten. Am Fall vun Problemer kuckt op: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/nl.json b/homeassistant/components/brother/translations/nl.json index 6440be77e75..97c506299a7 100644 --- a/homeassistant/components/brother/translations/nl.json +++ b/homeassistant/components/brother/translations/nl.json @@ -15,14 +15,13 @@ "data": { "host": "Host", "type": "Type printer" - }, - "description": "Zet Brother printerintegratie op. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Type printer" }, - "description": "Wilt u het Brother Printer {model} met serienummer {serial_number}' toevoegen aan Home Assistant?", + "description": "Wilt u de printer {model} met serienummer ` {serial_number} ` toevoegen aan Home Assistant?", "title": "Ontdekte Brother Printer" } } diff --git a/homeassistant/components/brother/translations/no.json b/homeassistant/components/brother/translations/no.json index 9d3618cad62..2f383701288 100644 --- a/homeassistant/components/brother/translations/no.json +++ b/homeassistant/components/brother/translations/no.json @@ -15,14 +15,13 @@ "data": { "host": "Vert", "type": "Skriver type" - }, - "description": "Sett opp Brother skriver integrasjonen. Hvis du har problemer med konfigurasjonen, bes\u00f8k dokumentasjonen her: [https://www.home-assistant.io/integrations/brother](https://www.home-assistant.io/integrations/brother)" + } }, "zeroconf_confirm": { "data": { "type": "Type skriver" }, - "description": "Vil du legge til Brother-skriveren {model} med serienummeret `{serial_number}` til Home Assistant?", + "description": "Vil du legge til skriveren {model} med serienummeret ` {serial_number} ` til Home Assistant?", "title": "Oppdaget Brother Skriver" } } diff --git a/homeassistant/components/brother/translations/pl.json b/homeassistant/components/brother/translations/pl.json index 20031bf94ad..159b0308ab7 100644 --- a/homeassistant/components/brother/translations/pl.json +++ b/homeassistant/components/brother/translations/pl.json @@ -15,14 +15,13 @@ "data": { "host": "Nazwa hosta lub adres IP", "type": "Typ drukarki" - }, - "description": "Konfiguracja integracji drukarek Brother. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a na stron\u0119: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Typ drukarki" }, - "description": "Czy chcesz doda\u0107 drukark\u0119 Brother {model} o numerze seryjnym `{serial_number}` do Home Assistanta?", + "description": "Czy chcesz doda\u0107 drukark\u0119 {model} o numerze seryjnym `{serial_number}` do Home Assistanta?", "title": "Wykryto drukark\u0119 Brother" } } diff --git a/homeassistant/components/brother/translations/pt-BR.json b/homeassistant/components/brother/translations/pt-BR.json index 33113d12881..5e938d90edd 100644 --- a/homeassistant/components/brother/translations/pt-BR.json +++ b/homeassistant/components/brother/translations/pt-BR.json @@ -15,14 +15,13 @@ "data": { "host": "Nome do host", "type": "Tipo de impressora" - }, - "description": "Configure a integra\u00e7\u00e3o da impressora Brother. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Tipo de impressora" }, - "description": "Deseja adicionar a impressora Brother {model} com n\u00famero de s\u00e9rie ` {serial_number} ` ao Home Assistant?", + "description": "Deseja adicionar a impressora {model} com n\u00famero de s\u00e9rie ` {serial_number} ` ao Home Assistant?", "title": "Impressora Brother descoberta" } } diff --git a/homeassistant/components/brother/translations/ru.json b/homeassistant/components/brother/translations/ru.json index 6fd15e30ac3..a9f6158ccf8 100644 --- a/homeassistant/components/brother/translations/ru.json +++ b/homeassistant/components/brother/translations/ru.json @@ -15,14 +15,13 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" - }, - "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438: https://www.home-assistant.io/integrations/brother." + } }, "zeroconf_confirm": { "data": { "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u043d\u0442\u0435\u0440 Brother {model} \u0441 \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u043d\u0442\u0435\u0440 {model} \u0441 \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d \u043f\u0440\u0438\u043d\u0442\u0435\u0440 Brother" } } diff --git a/homeassistant/components/brother/translations/sl.json b/homeassistant/components/brother/translations/sl.json index 53ce1423819..fcd53a65413 100644 --- a/homeassistant/components/brother/translations/sl.json +++ b/homeassistant/components/brother/translations/sl.json @@ -14,8 +14,7 @@ "data": { "host": "Gostiteljsko ime tiskalnika ali naslov IP", "type": "Vrsta tiskalnika" - }, - "description": "Nastavite integracijo tiskalnika Brother. \u010ce imate te\u017eave s konfiguracijo, pojdite na: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/sv.json b/homeassistant/components/brother/translations/sv.json index 3a049bb92c8..00d29aa3a0a 100644 --- a/homeassistant/components/brother/translations/sv.json +++ b/homeassistant/components/brother/translations/sv.json @@ -14,8 +14,7 @@ "data": { "host": "Skrivarens v\u00e4rdnamn eller IP-adress", "type": "Typ av skrivare" - }, - "description": "St\u00e4ll in Brother-skrivarintegration. Om du har problem med konfigurationen g\u00e5r du till: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/tr.json b/homeassistant/components/brother/translations/tr.json index c989844c6f7..291be1c4138 100644 --- a/homeassistant/components/brother/translations/tr.json +++ b/homeassistant/components/brother/translations/tr.json @@ -15,14 +15,13 @@ "data": { "host": "Sunucu", "type": "Yaz\u0131c\u0131n\u0131n t\u00fcr\u00fc" - }, - "description": "Brother yaz\u0131c\u0131 entegrasyonunu ayarlay\u0131n. Yap\u0131land\u0131rmayla ilgili sorunlar\u0131n\u0131z varsa \u015fu adrese gidin: https://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { "type": "Yaz\u0131c\u0131n\u0131n t\u00fcr\u00fc" }, - "description": "Seri numaras\u0131 ` {serial_number} ` olan Brother Yaz\u0131c\u0131 {model} }'i Home Assistant'a eklemek ister misiniz?", + "description": "Seri numaras\u0131 ` {serial_number} ` olan {model} yaz\u0131c\u0131y\u0131 Home Assistant'a eklemek istiyor musunuz?", "title": "Ke\u015ffedilen Brother Yaz\u0131c\u0131" } } diff --git a/homeassistant/components/brother/translations/uk.json b/homeassistant/components/brother/translations/uk.json index ac5943aa85c..89ce35d988e 100644 --- a/homeassistant/components/brother/translations/uk.json +++ b/homeassistant/components/brother/translations/uk.json @@ -15,8 +15,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" - }, - "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457: https://www.home-assistant.io/integrations/brother." + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/zh-Hans.json b/homeassistant/components/brother/translations/zh-Hans.json index 91e0c310dd1..5a0e6f80393 100644 --- a/homeassistant/components/brother/translations/zh-Hans.json +++ b/homeassistant/components/brother/translations/zh-Hans.json @@ -8,9 +8,6 @@ "snmp_error": "SNMP\u670d\u52a1\u5668\u5df2\u5173\u95ed\u6216\u4e0d\u652f\u6301\u6253\u5370\u3002" }, "step": { - "user": { - "description": "\u8bbe\u7f6e Brother \u6253\u5370\u673a\u96c6\u6210\u3002\u5982\u679c\u60a8\u9047\u5230\u95ee\u9898\uff0c\u8bf7\u8bbf\u95ee\uff1ahttps://www.home-assistant.io/integrations/brother" - }, "zeroconf_confirm": { "data": { "type": "\u6253\u5370\u673a\u7c7b\u578b" diff --git a/homeassistant/components/brother/translations/zh-Hant.json b/homeassistant/components/brother/translations/zh-Hant.json index 016d83309f2..1fdeedbb556 100644 --- a/homeassistant/components/brother/translations/zh-Hant.json +++ b/homeassistant/components/brother/translations/zh-Hant.json @@ -15,8 +15,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "type": "\u5370\u8868\u6a5f\u985e\u5225" - }, - "description": "\u8a2d\u5b9a Brother \u5370\u8868\u6a5f\u6574\u5408\u3002\u5047\u5982\u9700\u8981\u5354\u52a9\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/brother" + } }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index 8bbb11914f7..c38dfa0eb78 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -9,11 +9,9 @@ from brunt import BruntClientAsync, Thing from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -37,8 +35,6 @@ from .const import ( REGULAR_INTERVAL, ) -COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - async def async_setup_entry( hass: HomeAssistant, @@ -62,6 +58,12 @@ class BruntDevice(CoordinatorEntity, CoverEntity): Contains the common logic for all Brunt devices. """ + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + def __init__( self, coordinator: DataUpdateCoordinator, @@ -81,7 +83,6 @@ class BruntDevice(CoordinatorEntity, CoverEntity): self._attr_name = self._thing.name self._attr_device_class = CoverDeviceClass.BLIND - self._attr_supported_features = COVER_FEATURES self._attr_attribution = ATTRIBUTION self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self._attr_unique_id)}, diff --git a/homeassistant/components/bsblan/climate.py b/homeassistant/components/bsblan/climate.py index d7f4972c59e..16d8452f10b 100644 --- a/homeassistant/components/bsblan/climate.py +++ b/homeassistant/components/bsblan/climate.py @@ -11,13 +11,10 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_PRESET_MODE, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_ECO, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -32,12 +29,10 @@ _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 1 SCAN_INTERVAL = timedelta(seconds=20) -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - HVAC_MODES = [ - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, + HVACMode.AUTO, + HVACMode.HEAT, + HVACMode.OFF, ] PRESET_MODES = [ @@ -46,9 +41,9 @@ PRESET_MODES = [ ] HA_STATE_TO_BSBLAN = { - HVAC_MODE_AUTO: "1", - HVAC_MODE_HEAT: "3", - HVAC_MODE_OFF: "0", + HVACMode.AUTO: "1", + HVACMode.HEAT: "3", + HVACMode.OFF: "0", } BSBLAN_TO_HA_STATE = {value: key for key, value in HA_STATE_TO_BSBLAN.items()} @@ -76,7 +71,9 @@ async def async_setup_entry( class BSBLanClimate(ClimateEntity): """Defines a BSBLan climate device.""" - _attr_supported_features = SUPPORT_FLAGS + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) _attr_hvac_modes = HVAC_MODES _attr_preset_modes = PRESET_MODES diff --git a/homeassistant/components/bsblan/translations/hu.json b/homeassistant/components/bsblan/translations/hu.json index f04a0a3af0e..0c02cfb733d 100644 --- a/homeassistant/components/bsblan/translations/hu.json +++ b/homeassistant/components/bsblan/translations/hu.json @@ -17,7 +17,7 @@ "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "\u00c1ll\u00edtsa be a BSB-Lan eszk\u00f6zt az HomeAssistantba val\u00f3 integr\u00e1ci\u00f3hoz.", + "description": "\u00c1ll\u00edtsa be a BSB-Lan eszk\u00f6zt az Home Assistantba val\u00f3 integr\u00e1ci\u00f3hoz.", "title": "Csatlakoz\u00e1s a BSB-Lan eszk\u00f6zh\u00f6z" } } diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index 1955751d4fd..c5b2b3a790a 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -12,9 +12,9 @@ import voluptuous as vol from homeassistant.components.calendar import ( ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - CalendarEventDevice, + CalendarEntity, + CalendarEvent, extract_offset, - get_date, is_offset_reached, ) from homeassistant.const import ( @@ -104,7 +104,7 @@ def setup_platform( device_id = f"{cust_calendar[CONF_CALENDAR]} {cust_calendar[CONF_NAME]}" entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) calendar_devices.append( - WebDavCalendarEventDevice( + WebDavCalendarEntity( name, calendar, entity_id, days, True, cust_calendar[CONF_SEARCH] ) ) @@ -115,24 +115,24 @@ def setup_platform( device_id = calendar.name entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) calendar_devices.append( - WebDavCalendarEventDevice(name, calendar, entity_id, days) + WebDavCalendarEntity(name, calendar, entity_id, days) ) add_entities(calendar_devices, True) -class WebDavCalendarEventDevice(CalendarEventDevice): +class WebDavCalendarEntity(CalendarEntity): """A device for getting the next Task from a WebDav Calendar.""" def __init__(self, name, calendar, entity_id, days, all_day=False, search=None): """Create the WebDav Calendar Event Device.""" self.data = WebDavCalendarData(calendar, days, all_day, search) self.entity_id = entity_id - self._event = None + self._event: CalendarEvent | None = None self._attr_name = name @property - def event(self): + def event(self) -> CalendarEvent | None: """Return the next upcoming event.""" return self._event @@ -147,11 +147,11 @@ class WebDavCalendarEventDevice(CalendarEventDevice): if event is None: self._event = event return - (summary, offset) = extract_offset(event["summary"], OFFSET) - event["summary"] = summary + (summary, offset) = extract_offset(event.summary, OFFSET) + event.summary = summary self._event = event self._attr_extra_state_attributes = { - "offset_reached": is_offset_reached(get_date(event["start"]), offset) + "offset_reached": is_offset_reached(event.start_datetime_local, offset) } @@ -166,7 +166,9 @@ class WebDavCalendarData: self.search = search self.event = None - async def async_get_events(self, hass, start_date, end_date): + async def async_get_events( + self, hass: HomeAssistant, start_date: datetime, end_date: datetime + ) -> list[CalendarEvent]: """Get all events in a specific time frame.""" # Get event list from the current calendar vevent_list = await hass.async_add_executor_job( @@ -180,22 +182,15 @@ class WebDavCalendarData: vevent = event.instance.vevent if not self.is_matching(vevent, self.search): continue - uid = None - if hasattr(vevent, "uid"): - uid = vevent.uid.value - data = { - "uid": uid, - "summary": vevent.summary.value, - "start": self.get_hass_date(vevent.dtstart.value), - "end": self.get_hass_date(self.get_end_date(vevent)), - "location": self.get_attr_value(vevent, "location"), - "description": self.get_attr_value(vevent, "description"), - } - - data["start"] = get_date(data["start"]).isoformat() - data["end"] = get_date(data["end"]).isoformat() - - event_list.append(data) + event_list.append( + CalendarEvent( + summary=vevent.summary.value, + start=vevent.dtstart.value, + end=self.get_end_date(vevent), + location=self.get_attr_value(vevent, "location"), + description=self.get_attr_value(vevent, "description"), + ) + ) return event_list @@ -269,13 +264,13 @@ class WebDavCalendarData: return # Populate the entity attributes with the event values - self.event = { - "summary": vevent.summary.value, - "start": self.get_hass_date(vevent.dtstart.value), - "end": self.get_hass_date(self.get_end_date(vevent)), - "location": self.get_attr_value(vevent, "location"), - "description": self.get_attr_value(vevent, "description"), - } + self.event = CalendarEvent( + summary=vevent.summary.value, + start=vevent.dtstart.value, + end=self.get_end_date(vevent), + location=self.get_attr_value(vevent, "location"), + description=self.get_attr_value(vevent, "description"), + ) @staticmethod def is_matching(vevent, search): @@ -305,14 +300,6 @@ class WebDavCalendarData: WebDavCalendarData.get_end_date(vevent) ) - @staticmethod - def get_hass_date(obj): - """Return if the event matches.""" - if isinstance(obj, datetime): - return {"dateTime": obj.isoformat()} - - return {"date": obj.isoformat()} - @staticmethod def to_datetime(obj): """Return a datetime.""" diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 4449084373b..0f122fea55f 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -1,6 +1,7 @@ """Support for Google Calendar event device sensors.""" from __future__ import annotations +from dataclasses import dataclass import datetime from http import HTTPStatus import logging @@ -24,8 +25,6 @@ from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt -# mypy: allow-untyped-defs, no-check-untyped-defs - _LOGGER = logging.getLogger(__name__) DOMAIN = "calendar" @@ -75,6 +74,62 @@ def get_date(date: dict[str, Any]) -> datetime.datetime: return dt.as_local(parsed_datetime) +@dataclass +class CalendarEvent: + """An event on a calendar.""" + + start: datetime.date | datetime.datetime + end: datetime.date | datetime.datetime + summary: str + description: str | None = None + location: str | None = None + + @property + def start_datetime_local(self) -> datetime.datetime: + """Return event start time as a local datetime.""" + return _get_datetime_local(self.start) + + @property + def end_datetime_local(self) -> datetime.datetime: + """Return event end time as a local datetime.""" + return _get_datetime_local(self.end) + + @property + def all_day(self) -> bool: + """Return true if the event is an all day event.""" + return not isinstance(self.start, datetime.datetime) + + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the event.""" + data = { + "start": self.start.isoformat(), + "end": self.end.isoformat(), + "summary": self.summary, + "all_day": self.all_day, + } + if self.description: + data["description"] = self.description + if self.location: + data["location"] = self.location + return data + + +def _get_datetime_local( + dt_or_d: datetime.datetime | datetime.date, +) -> datetime.datetime: + """Convert a calendar event date/datetime to a datetime if needed.""" + if isinstance(dt_or_d, datetime.datetime): + return dt.as_local(dt_or_d) + return dt.start_of_local_day(dt_or_d) + + +def _get_api_date(dt_or_d: datetime.datetime | datetime.date) -> dict[str, str]: + """Convert a calendar event date/datetime to a datetime if needed.""" + if isinstance(dt_or_d, datetime.datetime): + return {"dateTime": dt.as_local(dt_or_d).isoformat()} + return {"date": dt_or_d.isoformat()} + + def normalize_event(event: dict[str, Any]) -> dict[str, Any]: """Normalize a calendar event.""" normalized_event: dict[str, Any] = {} @@ -134,7 +189,15 @@ def is_offset_reached( class CalendarEventDevice(Entity): - """Base class for calendar event entities.""" + """Legacy API for calendar event entities.""" + + def __init_subclass__(cls, **kwargs: Any) -> None: + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "CalendarEventDevice is deprecated, modify %s to extend CalendarEntity", + cls.__name__, + ) @property def event(self) -> dict[str, Any] | None: @@ -145,6 +208,7 @@ class CalendarEventDevice(Entity): @property def state_attributes(self) -> dict[str, Any] | None: """Return the entity state attributes.""" + if (event := self.event) is None: return None @@ -188,6 +252,53 @@ class CalendarEventDevice(Entity): raise NotImplementedError() +class CalendarEntity(Entity): + """Base class for calendar event entities.""" + + @property + def event(self) -> CalendarEvent | None: + """Return the next upcoming event.""" + raise NotImplementedError() + + @final + @property + def state_attributes(self) -> dict[str, Any] | None: + """Return the entity state attributes.""" + if (event := self.event) is None: + return None + + return { + "message": event.summary, + "all_day": event.all_day, + "start_time": event.start_datetime_local.strftime(DATE_STR_FORMAT), + "end_time": event.end_datetime_local.strftime(DATE_STR_FORMAT), + "location": event.location if event.location else "", + "description": event.description if event.description else "", + } + + @property + def state(self) -> str | None: + """Return the state of the calendar event.""" + if (event := self.event) is None: + return STATE_OFF + + now = dt.now() + + if event.start_datetime_local <= now < event.end_datetime_local: + return STATE_ON + + return STATE_OFF + + async def async_get_events( + self, + hass: HomeAssistant, + start_date: datetime.datetime, + end_date: datetime.datetime, + ) -> list[CalendarEvent]: + """Return calendar events within a datetime range.""" + raise NotImplementedError() + + class CalendarEventView(http.HomeAssistantView): """View to retrieve calendar content.""" @@ -205,7 +316,6 @@ class CalendarEventView(http.HomeAssistantView): end = request.query.get("end") if start is None or end is None or entity is None: return web.Response(status=HTTPStatus.BAD_REQUEST) - assert isinstance(entity, CalendarEventDevice) try: start_date = dt.parse_datetime(start) end_date = dt.parse_datetime(end) @@ -213,10 +323,30 @@ class CalendarEventView(http.HomeAssistantView): return web.Response(status=HTTPStatus.BAD_REQUEST) if start_date is None or end_date is None: return web.Response(status=HTTPStatus.BAD_REQUEST) - event_list = await entity.async_get_events( + + # Compatibility shim for old API + if isinstance(entity, CalendarEventDevice): + event_list = await entity.async_get_events( + request.app["hass"], start_date, end_date + ) + return self.json(event_list) + + if not isinstance(entity, CalendarEntity): + return web.Response(status=HTTPStatus.BAD_REQUEST) + + calendar_event_list = await entity.async_get_events( request.app["hass"], start_date, end_date ) - return self.json(event_list) + return self.json( + [ + { + "summary": event.summary, + "start": _get_api_date(event.start), + "end": _get_api_date(event.end), + } + for event in calendar_event_list + ] + ) class CalendarListView(http.HomeAssistantView): diff --git a/homeassistant/components/calendar/trigger.py b/homeassistant/components/calendar/trigger.py new file mode 100644 index 00000000000..3540a9f5148 --- /dev/null +++ b/homeassistant/components/calendar/trigger.py @@ -0,0 +1,182 @@ +"""Offer calendar automation rules.""" +from __future__ import annotations + +import datetime +import logging +from typing import Any + +import voluptuous as vol + +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) +from homeassistant.const import CONF_ENTITY_ID, CONF_EVENT, CONF_PLATFORM +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.event import ( + async_track_point_in_utc_time, + async_track_time_interval, +) +from homeassistant.helpers.typing import ConfigType +from homeassistant.util import dt as dt_util + +from . import DOMAIN, CalendarEntity, CalendarEvent + +_LOGGER = logging.getLogger(__name__) + +EVENT_START = "start" +EVENT_END = "end" +UPDATE_INTERVAL = datetime.timedelta(minutes=15) + +TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_PLATFORM): DOMAIN, + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_EVENT, default=EVENT_START): vol.In({EVENT_START, EVENT_END}), + } +) + + +class CalendarEventListener: + """Helper class to listen to calendar events.""" + + def __init__( + self, + hass: HomeAssistant, + job: HassJob, + trigger_data: dict[str, Any], + entity: CalendarEntity, + event_type: str, + ) -> None: + """Initialize CalendarEventListener.""" + self._hass = hass + self._job = job + self._trigger_data = trigger_data + self._entity = entity + self._unsub_event: CALLBACK_TYPE | None = None + self._unsub_refresh: CALLBACK_TYPE | None = None + # Upcoming set of events with their trigger time + self._events: list[tuple[datetime.datetime, CalendarEvent]] = [] + self._event_type = event_type + + async def async_attach(self) -> None: + """Attach a calendar event listener.""" + now = dt_util.utcnow() + await self._fetch_events(now) + self._unsub_refresh = async_track_time_interval( + self._hass, self._handle_refresh, UPDATE_INTERVAL + ) + self._listen_next_calendar_event() + + @callback + def async_detach(self) -> None: + """Detach the calendar event listener.""" + self._clear_event_listener() + if self._unsub_refresh: + self._unsub_refresh() + self._unsub_refresh = None + + async def _fetch_events(self, last_endtime: datetime.datetime) -> None: + """Update the set of eligible events.""" + # Event time ranges are exclusive so the end time is expanded by 1sec + end_time = last_endtime + UPDATE_INTERVAL + datetime.timedelta(seconds=1) + _LOGGER.debug("Fetching events between %s, %s", last_endtime, end_time) + events = await self._entity.async_get_events(self._hass, last_endtime, end_time) + + # Build list of events and the appropriate time to trigger an alarm. The + # returned events may have already started but matched the start/end time + # filtering above, so exclude any events that have already passed the + # trigger time. + event_list = [] + for event in events: + event_time = ( + event.start_datetime_local + if self._event_type == EVENT_START + else event.end_datetime_local + ) + if event_time > last_endtime: + event_list.append((event_time, event)) + event_list.sort(key=lambda x: x[0]) + self._events = event_list + _LOGGER.debug("Populated event list %s", self._events) + + @callback + def _listen_next_calendar_event(self) -> None: + """Set up the calendar event listener.""" + if not self._events: + return + + (event_datetime, _event) = self._events[0] + _LOGGER.debug("Scheduling next event trigger @ %s", event_datetime) + self._unsub_event = async_track_point_in_utc_time( + self._hass, + self._handle_calendar_event, + event_datetime, + ) + + def _clear_event_listener(self) -> None: + """Reset the event listener.""" + if self._unsub_event: + self._unsub_event() + self._unsub_event = None + + async def _handle_calendar_event(self, now: datetime.datetime) -> None: + """Handle calendar event.""" + _LOGGER.debug("Calendar event @ %s", now) + self._dispatch_events(now) + self._clear_event_listener() + self._listen_next_calendar_event() + + def _dispatch_events(self, now: datetime.datetime) -> None: + """Dispatch all events that are eligible to fire.""" + while self._events and self._events[0][0] <= now: + (_fire_time, event) = self._events.pop(0) + _LOGGER.debug("Event: %s", event) + self._hass.async_run_hass_job( + self._job, + {"trigger": {**self._trigger_data, "calendar_event": event.as_dict()}}, + ) + + async def _handle_refresh(self, now: datetime.datetime) -> None: + """Handle core config update.""" + _LOGGER.debug("Refresh events @ %s", now) + # Dispatch any eligible events in the boundary case where refresh + # fires before the calendar event. + self._dispatch_events(now) + self._clear_event_listener() + await self._fetch_events(now) + self._listen_next_calendar_event() + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: + """Attach trigger for the specified calendar.""" + entity_id = config[CONF_ENTITY_ID] + event_type = config[CONF_EVENT] + + component: EntityComponent = hass.data[DOMAIN] + if not (entity := component.get_entity(entity_id)) or not isinstance( + entity, CalendarEntity + ): + raise HomeAssistantError( + f"Entity does not exist {entity_id} or is not a calendar entity" + ) + + trigger_data = { + **automation_info["trigger_data"], + "platform": DOMAIN, + "event": event_type, + } + + listener = CalendarEventListener( + hass, HassJob(action), trigger_data, entity, event_type + ) + await listener.async_attach() + return listener.async_detach diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index f3258681b52..2079e962459 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -8,6 +8,7 @@ from collections.abc import Awaitable, Callable, Iterable from contextlib import suppress from dataclasses import dataclass from datetime import datetime, timedelta +from enum import IntEnum from functools import partial import hashlib import logging @@ -53,7 +54,7 @@ from homeassistant.helpers.network import get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from .const import ( +from .const import ( # noqa: F401 CAMERA_IMAGE_TIMEOUT, CAMERA_STREAM_SOURCE_TIMEOUT, CONF_DURATION, @@ -64,6 +65,7 @@ from .const import ( SERVICE_RECORD, STREAM_TYPE_HLS, STREAM_TYPE_WEB_RTC, + StreamType, ) from .img_util import scale_jpeg_camera_image from .prefs import CameraPreferences @@ -88,7 +90,16 @@ STATE_RECORDING: Final = "recording" STATE_STREAMING: Final = "streaming" STATE_IDLE: Final = "idle" -# Bitfield of features supported by the camera entity + +class CameraEntityFeature(IntEnum): + """Supported features of the camera entity.""" + + ON_OFF = 1 + STREAM = 2 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Pleease use the CameraEntityFeature enum instead. SUPPORT_ON_OFF: Final = 1 SUPPORT_STREAM: Final = 2 @@ -426,7 +437,7 @@ class Camera(Entity): # Entity Properties _attr_brand: str | None = None _attr_frame_interval: float = MIN_STREAM_INTERVAL - _attr_frontend_stream_type: str | None + _attr_frontend_stream_type: StreamType | None _attr_is_on: bool = True _attr_is_recording: bool = False _attr_is_streaming: bool = False @@ -490,7 +501,7 @@ class Camera(Entity): return self._attr_frame_interval @property - def frontend_stream_type(self) -> str | None: + def frontend_stream_type(self) -> StreamType | None: """Return the type of stream supported by this camera. A camera may have a single stream type which is used to inform the @@ -499,11 +510,11 @@ class Camera(Entity): """ if hasattr(self, "_attr_frontend_stream_type"): return self._attr_frontend_stream_type - if not self.supported_features & SUPPORT_STREAM: + if not self.supported_features & CameraEntityFeature.STREAM: return None if self._rtsp_to_webrtc: - return STREAM_TYPE_WEB_RTC - return STREAM_TYPE_HLS + return StreamType.WEB_RTC + return StreamType.HLS @property def available(self) -> bool: @@ -535,15 +546,16 @@ class Camera(Entity): async def stream_source(self) -> str | None: """Return the source of the stream. - This is used by cameras with SUPPORT_STREAM and STREAM_TYPE_HLS. + This is used by cameras with CameraEntityFeature.STREAM + and StreamType.HLS. """ - # pylint: disable=no-self-use return None async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None: """Handle the WebRTC offer and return an answer. - This is used by cameras with SUPPORT_STREAM and STREAM_TYPE_WEB_RTC. + This is used by cameras with CameraEntityFeature.STREAM + and StreamType.WEB_RTC. Integrations can override with a native WebRTC implementation. """ @@ -682,7 +694,7 @@ class Camera(Entity): async def _async_use_rtsp_to_webrtc(self) -> bool: """Determine if a WebRTC provider can be used for the camera.""" - if not self.supported_features & SUPPORT_STREAM: + if not self.supported_features & CameraEntityFeature.STREAM: return False if DATA_RTSP_TO_WEB_RTC not in self.hass.data: return False @@ -858,7 +870,7 @@ async def ws_camera_web_rtc_offer( entity_id = msg["entity_id"] offer = msg["offer"] camera = _get_camera_from_entity_id(hass, entity_id) - if camera.frontend_stream_type != STREAM_TYPE_WEB_RTC: + if camera.frontend_stream_type != StreamType.WEB_RTC: connection.send_error( msg["id"], "web_rtc_offer_failed", diff --git a/homeassistant/components/camera/const.py b/homeassistant/components/camera/const.py index cef8773d974..fafed8a4266 100644 --- a/homeassistant/components/camera/const.py +++ b/homeassistant/components/camera/const.py @@ -1,6 +1,8 @@ """Constants for Camera component.""" from typing import Final +from homeassistant.backports.enum import StrEnum + DOMAIN: Final = "camera" DATA_CAMERA_PREFS: Final = "camera_prefs" @@ -16,11 +18,23 @@ CONF_DURATION: Final = "duration" CAMERA_STREAM_SOURCE_TIMEOUT: Final = 10 CAMERA_IMAGE_TIMEOUT: Final = 10 -# A camera that supports CAMERA_SUPPORT_STREAM may have a single stream -# type which is used to inform the frontend which player to use. -# Streams with RTSP sources typically use the stream component which uses -# HLS for display. WebRTC streams use the home assistant core for a signal -# path to initiate a stream, but the stream itself is between the client and -# device. + +class StreamType(StrEnum): + """Camera stream type. + + A camera that supports CAMERA_SUPPORT_STREAM may have a single stream + type which is used to inform the frontend which player to use. + Streams with RTSP sources typically use the stream component which uses + HLS for display. WebRTC streams use the home assistant core for a signal + path to initiate a stream, but the stream itself is between the client and + device. + """ + + HLS = "hls" + WEB_RTC = "web_rtc" + + +# These constants are deprecated as of Home Assistant 2022.5 +# Please use the StreamType enum instead. STREAM_TYPE_HLS = "hls" STREAM_TYPE_WEB_RTC = "web_rtc" diff --git a/homeassistant/components/camera/diagnostics.py b/homeassistant/components/camera/diagnostics.py new file mode 100644 index 00000000000..1edda5079b4 --- /dev/null +++ b/homeassistant/components/camera/diagnostics.py @@ -0,0 +1,31 @@ +"""Diagnostics for camera.""" + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er + +from . import _get_camera_from_entity_id +from .const import DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + entity_registry = er.async_get(hass) + entities = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + diagnostics = {} + for entity in entities: + if entity.domain != DOMAIN: + continue + try: + camera = _get_camera_from_entity_id(hass, entity.entity_id) + except HomeAssistantError: + continue + diagnostics[entity.entity_id] = ( + camera.stream.get_diagnostics() if camera.stream else {} + ) + return diagnostics diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index e65aabe459d..ffa1962f9ef 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -21,7 +21,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_component import EntityComponent from . import Camera, _async_stream_endpoint_url -from .const import DOMAIN, STREAM_TYPE_HLS +from .const import DOMAIN, StreamType async def async_get_media_source(hass: HomeAssistant) -> CameraMediaSource: @@ -52,7 +52,7 @@ class CameraMediaSource(MediaSource): f"/api/camera_proxy_stream/{camera.entity_id}", camera.content_type ) - if stream_type != STREAM_TYPE_HLS: + if stream_type != StreamType.HLS: raise Unresolvable("Camera does not support MJPEG or HLS streaming.") if "stream" not in self.hass.config.components: @@ -86,7 +86,7 @@ class CameraMediaSource(MediaSource): if stream_type is None: content_type = camera.content_type - elif can_stream_hls and stream_type == STREAM_TYPE_HLS: + elif can_stream_hls and stream_type == StreamType.HLS: content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER] else: diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index 165bae0b497..d33e98008d6 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -10,11 +10,9 @@ from canary.api import ( Location, ) -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -54,7 +52,9 @@ class CanaryAlarm( """Representation of a Canary alarm control panel.""" _attr_supported_features = ( - SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT ) def __init__( diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index 3c6ba2af43f..5c6f0fee62a 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -1,13 +1,26 @@ """Helpers to deal with Cast devices.""" from __future__ import annotations +import asyncio +import configparser +from dataclasses import dataclass +import logging from typing import Optional +from urllib.parse import urlparse +import aiohttp import attr +import pychromecast from pychromecast import dial from pychromecast.const import CAST_TYPE_GROUP from pychromecast.models import CastInfo +from homeassistant.helpers import aiohttp_client + +_LOGGER = logging.getLogger(__name__) + +_PLS_SECTION_PLAYLIST = "playlist" + @attr.s(slots=True, frozen=True) class ChromecastInfo: @@ -86,7 +99,12 @@ class ChromeCastZeroconf: return cls.__zconf -class CastStatusListener: +class CastStatusListener( + pychromecast.controllers.media.MediaStatusListener, + pychromecast.controllers.multizone.MultiZoneManagerListener, + pychromecast.controllers.receiver.CastStatusListener, + pychromecast.socket_client.ConnectionStatusListener, +): """Helper class to handle pychromecast status callbacks. Necessary because a CastDevice entity or dynamic group can create a new @@ -112,23 +130,27 @@ class CastStatusListener: if not cast_device._cast_info.is_audio_group: self._mz_mgr.register_listener(chromecast.uuid, self) - def new_cast_status(self, cast_status): + def new_cast_status(self, status): """Handle reception of a new CastStatus.""" if self._valid: - self._cast_device.new_cast_status(cast_status) + self._cast_device.new_cast_status(status) - def new_media_status(self, media_status): + def new_media_status(self, status): """Handle reception of a new MediaStatus.""" if self._valid: - self._cast_device.new_media_status(media_status) + self._cast_device.new_media_status(status) - def new_connection_status(self, connection_status): + def load_media_failed(self, item, error_code): + """Handle reception of a new MediaStatus.""" + if self._valid: + self._cast_device.load_media_failed(item, error_code) + + def new_connection_status(self, status): """Handle reception of a new ConnectionStatus.""" if self._valid: - self._cast_device.new_connection_status(connection_status) + self._cast_device.new_connection_status(status) - @staticmethod - def added_to_multizone(group_uuid): + def added_to_multizone(self, group_uuid): """Handle the cast added to a group.""" def removed_from_multizone(self, group_uuid): @@ -155,3 +177,143 @@ class CastStatusListener: else: self._mz_mgr.deregister_listener(self._uuid, self) self._valid = False + + +class PlaylistError(Exception): + """Exception wrapper for pls and m3u helpers.""" + + +class PlaylistSupported(PlaylistError): + """The playlist is supported by cast devices and should not be parsed.""" + + +@dataclass +class PlaylistItem: + """Playlist item.""" + + length: str | None + title: str | None + url: str + + +def _is_url(url): + """Validate the URL can be parsed and at least has scheme + netloc.""" + result = urlparse(url) + return all([result.scheme, result.netloc]) + + +async def _fetch_playlist(hass, url): + """Fetch a playlist from the given url.""" + try: + session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False) + async with session.get(url, timeout=5) as resp: + charset = resp.charset or "utf-8" + try: + playlist_data = (await resp.content.read(64 * 1024)).decode(charset) + except ValueError as err: + raise PlaylistError(f"Could not decode playlist {url}") from err + except asyncio.TimeoutError as err: + raise PlaylistError(f"Timeout while fetching playlist {url}") from err + except aiohttp.client_exceptions.ClientError as err: + raise PlaylistError(f"Error while fetching playlist {url}") from err + + return playlist_data + + +async def parse_m3u(hass, url): + """Very simple m3u parser. + + Based on https://github.com/dvndrsn/M3uParser/blob/master/m3uparser.py + """ + m3u_data = await _fetch_playlist(hass, url) + m3u_lines = m3u_data.splitlines() + + playlist = [] + + length = None + title = None + + for line in m3u_lines: + line = line.strip() + if line.startswith("#EXTINF:"): + # Get length and title from #EXTINF line + info = line.split("#EXTINF:")[1].split(",", 1) + if len(info) != 2: + _LOGGER.warning("Ignoring invalid extinf %s in playlist %s", line, url) + continue + length = info[0].split(" ", 1) + title = info[1].strip() + elif line.startswith("#EXT-X-VERSION:"): + # HLS stream, supported by cast devices + raise PlaylistSupported("HLS") + elif line.startswith("#"): + # Ignore other extensions + continue + elif len(line) != 0: + # Get song path from all other, non-blank lines + if not _is_url(line): + raise PlaylistError(f"Invalid item {line} in playlist {url}") + playlist.append(PlaylistItem(length=length, title=title, url=line)) + # reset the song variables so it doesn't use the same EXTINF more than once + length = None + title = None + + return playlist + + +async def parse_pls(hass, url): + """Very simple pls parser. + + Based on https://github.com/mariob/plsparser/blob/master/src/plsparser.py + """ + pls_data = await _fetch_playlist(hass, url) + + pls_parser = configparser.ConfigParser() + try: + pls_parser.read_string(pls_data, url) + except configparser.Error as err: + raise PlaylistError(f"Can't parse playlist {url}") from err + + if ( + _PLS_SECTION_PLAYLIST not in pls_parser + or pls_parser[_PLS_SECTION_PLAYLIST].getint("Version") != 2 + ): + raise PlaylistError(f"Invalid playlist {url}") + + try: + num_entries = pls_parser.getint(_PLS_SECTION_PLAYLIST, "NumberOfEntries") + except (configparser.NoOptionError, ValueError) as err: + raise PlaylistError(f"Invalid NumberOfEntries in playlist {url}") from err + + playlist_section = pls_parser[_PLS_SECTION_PLAYLIST] + + playlist = [] + for entry in range(1, num_entries + 1): + file_option = f"File{entry}" + if file_option not in playlist_section: + _LOGGER.warning("Missing %s in pls from %s", file_option, url) + continue + item_url = playlist_section[file_option] + if not _is_url(item_url): + raise PlaylistError(f"Invalid item {item_url} in playlist {url}") + playlist.append( + PlaylistItem( + length=playlist_section.get(f"Length{entry}"), + title=playlist_section.get(f"Title{entry}"), + url=item_url, + ) + ) + return playlist + + +async def parse_playlist(hass, url): + """Parse an m3u or pls playlist.""" + if url.endswith(".m3u") or url.endswith(".m3u8"): + playlist = await parse_m3u(hass, url) + else: + playlist = await parse_pls(hass, url) + + if not playlist: + raise PlaylistError(f"Empty playlist {url}") + + return playlist diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 1e933d5e10e..389b837f200 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==11.0.0"], + "requirements": ["pychromecast==12.0.0"], "after_dependencies": [ "cloud", "http", diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 8d86edd7ebb..e63fedd3598 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -10,6 +10,12 @@ import logging import pychromecast from pychromecast.controllers.homeassistant import HomeAssistantController +from pychromecast.controllers.media import ( + MEDIA_PLAYER_ERROR_CODES, + MEDIA_PLAYER_STATE_BUFFERING, + MEDIA_PLAYER_STATE_PLAYING, + MEDIA_PLAYER_STATE_UNKNOWN, +) from pychromecast.controllers.multizone import MultizoneManager from pychromecast.controllers.receiver import VOLUME_CONTROL_TYPE_FIXED from pychromecast.quick_play import quick_play @@ -25,6 +31,7 @@ from homeassistant.components.media_player import ( BrowseError, BrowseMedia, MediaPlayerEntity, + MediaPlayerEntityFeature, async_process_play_media_url, ) from homeassistant.components.media_player.const import ( @@ -33,23 +40,12 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CAST_APP_ID_HOMEASSISTANT_LOVELACE, EVENT_HOMEASSISTANT_STOP, + STATE_BUFFERING, STATE_IDLE, STATE_OFF, STATE_PAUSED, @@ -75,7 +71,14 @@ from .const import ( SIGNAL_HASS_CAST_SHOW_VIEW, ) from .discovery import setup_internal_discovery -from .helpers import CastStatusListener, ChromecastInfo, ChromeCastZeroconf +from .helpers import ( + CastStatusListener, + ChromecastInfo, + ChromeCastZeroconf, + PlaylistError, + PlaylistSupported, + parse_playlist, +) _LOGGER = logging.getLogger(__name__) @@ -83,8 +86,6 @@ APP_IDS_UNRELIABLE_MEDIA_INFO = ("Netflix",) CAST_SPLASH = "https://www.home-assistant.io/images/cast/splash.png" -SUPPORT_CAST = SUPPORT_PLAY_MEDIA | SUPPORT_TURN_OFF - ENTITY_SCHEMA = vol.All( vol.Schema( { @@ -396,6 +397,17 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): self.media_status_received = dt_util.utcnow() self.schedule_update_ha_state() + def load_media_failed(self, item, error_code): + """Handle load media failed.""" + _LOGGER.debug( + "[%s %s] Load media failed with code %s(%s) for item %s", + self.entity_id, + self._cast_info.friendly_name, + error_code, + MEDIA_PLAYER_ERROR_CODES.get(error_code, "unknown code"), + item, + ) + def new_connection_status(self, connection_status): """Handle updates of connection status.""" _LOGGER.debug( @@ -447,10 +459,13 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): media_status = self.media_status media_controller = self._chromecast.media_controller - if media_status is None or media_status.player_state == "UNKNOWN": + if ( + media_status is None + or media_status.player_state == MEDIA_PLAYER_STATE_UNKNOWN + ): groups = self.mz_media_status for k, val in groups.items(): - if val and val.player_state != "UNKNOWN": + if val and val.player_state != MEDIA_PLAYER_STATE_UNKNOWN: media_controller = self.mz_mgr.get_multizone_mediacontroller(k) break @@ -595,14 +610,13 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): media_id = sourced_media.url extra = kwargs.get(ATTR_MEDIA_EXTRA, {}) - metadata = extra.get("metadata") # Handle media supported by a known cast app if media_type == CAST_DOMAIN: try: app_data = json.loads(media_id) - if metadata is not None: - app_data["metadata"] = extra.get("metadata") + if metadata := extra.get("metadata"): + app_data["metadata"] = metadata except json.JSONDecodeError: _LOGGER.error("Invalid JSON in media_content_id") raise @@ -653,9 +667,51 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): "hlsVideoSegmentFormat": "fmp4", }, } + elif ( + media_id.endswith(".m3u") + or media_id.endswith(".m3u8") + or media_id.endswith(".pls") + ): + try: + playlist = await parse_playlist(self.hass, media_id) + _LOGGER.debug( + "[%s %s] Playing item %s from playlist %s", + self.entity_id, + self._cast_info.friendly_name, + playlist[0].url, + media_id, + ) + media_id = playlist[0].url + if title := playlist[0].title: + extra = { + **extra, + "metadata": {"title": title}, + } + except PlaylistSupported as err: + _LOGGER.debug( + "[%s %s] Playlist %s is supported: %s", + self.entity_id, + self._cast_info.friendly_name, + media_id, + err, + ) + except PlaylistError as err: + _LOGGER.warning( + "[%s %s] Failed to parse playlist %s: %s", + self.entity_id, + self._cast_info.friendly_name, + media_id, + err, + ) # Default to play with the default media receiver app_data = {"media_id": media_id, "media_type": media_type, **extra} + _LOGGER.debug( + "[%s %s] Playing %s with default_media_receiver", + self.entity_id, + self._cast_info.friendly_name, + app_data, + ) await self.hass.async_add_executor_job( quick_play, self._chromecast, "default_media_receiver", app_data ) @@ -669,10 +725,13 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): media_status = self.media_status media_status_received = self.media_status_received - if media_status is None or media_status.player_state == "UNKNOWN": + if ( + media_status is None + or media_status.player_state == MEDIA_PLAYER_STATE_UNKNOWN + ): groups = self.mz_media_status for k, val in groups.items(): - if val and val.player_state != "UNKNOWN": + if val and val.player_state != MEDIA_PLAYER_STATE_UNKNOWN: media_status = val media_status_received = self.mz_media_status_received[k] break @@ -686,8 +745,10 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): if self.app_id == CAST_APP_ID_HOMEASSISTANT_LOVELACE: return STATE_PLAYING if (media_status := self._media_status()[0]) is not None: - if media_status.player_is_playing: + if media_status.player_state == MEDIA_PLAYER_STATE_PLAYING: return STATE_PLAYING + if media_status.player_state == MEDIA_PLAYER_STATE_BUFFERING: + return STATE_BUFFERING if media_status.player_is_paused: return STATE_PAUSED if media_status.player_is_idle: @@ -806,30 +867,38 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): @property def supported_features(self): """Flag media player features that are supported.""" - support = SUPPORT_CAST + support = ( + MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + ) media_status = self._media_status()[0] - if self._chromecast and self._chromecast.cast_type in ( - pychromecast.const.CAST_TYPE_CHROMECAST, - pychromecast.const.CAST_TYPE_AUDIO, - ): - support |= SUPPORT_TURN_ON - if ( self.cast_status and self.cast_status.volume_control_type != VOLUME_CONTROL_TYPE_FIXED ): - support |= SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET + support |= ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + ) if media_status and self.app_id != CAST_APP_ID_HOMEASSISTANT_LOVELACE: - support |= SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_STOP + support |= ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + ) if media_status.supports_queue_next: - support |= SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + support |= ( + MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + ) if media_status.supports_seek: - support |= SUPPORT_SEEK + support |= MediaPlayerEntityFeature.SEEK if "media_source" in self.hass.config.components: - support |= SUPPORT_BROWSE_MEDIA + support |= MediaPlayerEntityFeature.BROWSE_MEDIA return support diff --git a/homeassistant/components/cast/translations/cs.json b/homeassistant/components/cast/translations/cs.json index f04465341e3..cfcc3594575 100644 --- a/homeassistant/components/cast/translations/cs.json +++ b/homeassistant/components/cast/translations/cs.json @@ -3,10 +3,42 @@ "abort": { "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, + "error": { + "invalid_known_hosts": "Zn\u00e1m\u00ed hostitel\u00e9 mus\u00ed b\u00fdt seznam hostitel\u016f odd\u011blen\u00fd \u010d\u00e1rkou." + }, "step": { + "config": { + "data": { + "known_hosts": "Zn\u00e1m\u00ed hostitel\u00e9" + }, + "description": "Zn\u00e1m\u00ed hostitel\u00e9 - seznam n\u00e1zv\u016f hostitel\u016f nebo IP adres za\u0159\u00edzen\u00ed odd\u011blen\u00fd \u010d\u00e1rkou, kter\u00fd se pou\u017eije, pokud nefunguje zji\u0161\u0165ov\u00e1n\u00ed pomoc\u00ed mDNS.", + "title": "Konfigurace Google Cast" + }, "confirm": { "description": "Chcete za\u010d\u00edt nastavovat?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Zn\u00e1m\u00ed hostitel\u00e9 mus\u00ed b\u00fdt seznam hostitel\u016f odd\u011blen\u00fd \u010d\u00e1rkou." + }, + "step": { + "advanced_options": { + "data": { + "ignore_cec": "Ignorovat CEC", + "uuid": "Povolen\u00e9 UUID" + }, + "description": "Allowed UUIDs - \u010d\u00e1rkou odd\u011blen\u00fd seznam UUID za\u0159\u00edzen\u00ed Cast, kter\u00e9 chcete p\u0159idat do aplikace Home Assistant. Pou\u017eijte pouze v p\u0159\u00edpad\u011b, \u017ee nechcete p\u0159idat v\u0161echna dostupn\u00e1 za\u0159\u00edzen\u00ed Cast.\nIgnorovat CEC - \u010c\u00e1rkou odd\u011blen\u00fd seznam za\u0159\u00edzen\u00ed Chromecast, kter\u00e1 maj\u00ed p\u0159i ur\u010dov\u00e1n\u00ed aktivn\u00edho vstupu ignorovat data CEC. Tento \u00fadaj bude p\u0159ed\u00e1n do pychromecast.IGNORE_CEC.", + "title": "Pokro\u010dil\u00e1 konfigurace Google Cast" + }, + "basic_options": { + "data": { + "known_hosts": "Zn\u00e1m\u00ed hostitel\u00e9" + }, + "description": "Zn\u00e1m\u00ed hostitel\u00e9 - seznam n\u00e1zv\u016f hostitel\u016f nebo IP adres za\u0159\u00edzen\u00ed odd\u011blen\u00fd \u010d\u00e1rkou, kter\u00fd se pou\u017eije, pokud nefunguje zji\u0161\u0165ov\u00e1n\u00ed pomoc\u00ed mDNS.", + "title": "Konfigurace Google Cast" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/channels/media_player.py b/homeassistant/components/channels/media_player.py index d3a076f4a88..36fa2fe7ba0 100644 --- a/homeassistant/components/channels/media_player.py +++ b/homeassistant/components/channels/media_player.py @@ -4,20 +4,16 @@ from __future__ import annotations from pychannels import Channels import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, ) from homeassistant.const import ( ATTR_SECONDS, @@ -39,17 +35,6 @@ DATA_CHANNELS = "channels" DEFAULT_NAME = "Channels" DEFAULT_PORT = 57000 -FEATURE_SUPPORT = ( - SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_VOLUME_MUTE - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -91,6 +76,17 @@ async def async_setup_platform( class ChannelsPlayer(MediaPlayerEntity): """Representation of a Channels instance.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, name, host, port): """Initialize the Channels app.""" @@ -215,11 +211,6 @@ class ChannelsPlayer(MediaPlayerEntity): return None - @property - def supported_features(self): - """Flag of media commands that are supported.""" - return FEATURE_SUPPORT - def mute_volume(self, mute): """Mute (true) or unmute (false) player.""" if mute != self.muted: diff --git a/homeassistant/components/clementine/media_player.py b/homeassistant/components/clementine/media_player.py index 2632d95427b..6b0f74a9e63 100644 --- a/homeassistant/components/clementine/media_player.py +++ b/homeassistant/components/clementine/media_player.py @@ -7,17 +7,12 @@ import time from clementineremote import ClementineRemote import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_HOST, @@ -37,16 +32,6 @@ DEFAULT_PORT = 5500 SCAN_INTERVAL = timedelta(seconds=5) -SUPPORT_CLEMENTINE = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_VOLUME_SET - | SUPPORT_NEXT_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -78,7 +63,15 @@ class ClementineDevice(MediaPlayerEntity): """Representation of Clementine Player.""" _attr_media_content_type = MEDIA_TYPE_MUSIC - _attr_supported_features = SUPPORT_CLEMENTINE + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + ) def __init__(self, client, name): """Initialize the Clementine device.""" diff --git a/homeassistant/components/climacell/translations/ca.json b/homeassistant/components/climacell/translations/ca.json index fa924ae8272..78d43991e90 100644 --- a/homeassistant/components/climacell/translations/ca.json +++ b/homeassistant/components/climacell/translations/ca.json @@ -25,7 +25,7 @@ "data": { "timestep": "Minuts entre previsions NowCast" }, - "description": "Si decideixes activar l'entitat de predicci\u00f3 \"nowcast\", podr\u00e0s configurar l'interval en minuts entre cada previsi\u00f3. El nombre de previsions proporcionades dep\u00e8n d'aquest interval de minuts.", + "description": "Si decideixes activar l'entitat de previsi\u00f3 `nowcast`, podr\u00e0s configurar l'interval en minuts entre cada previsi\u00f3. El nombre de previsions proporcionades dep\u00e8n d'aquest interval de minuts.", "title": "Actualitzaci\u00f3 d'opcions de ClimaCell" } } diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json index eb5f3f73faf..01e9a81647e 100644 --- a/homeassistant/components/climacell/translations/de.json +++ b/homeassistant/components/climacell/translations/de.json @@ -23,7 +23,7 @@ "step": { "init": { "data": { - "timestep": "Minuten zwischen den Kurzvorhersagen" + "timestep": "Minuten zwischen den NowCast Kurzvorhersagen" }, "description": "Wenn du die Vorhersage-Entitit\u00e4t \"Kurzvorhersage\" aktivierst, kannst du die Anzahl der Minuten zwischen den einzelnen Vorhersagen konfigurieren. Die Anzahl der bereitgestellten Vorhersagen h\u00e4ngt von der Anzahl der zwischen den Vorhersagen gew\u00e4hlten Minuten ab.", "title": "ClimaCell-Optionen aktualisieren" diff --git a/homeassistant/components/climacell/translations/en.json b/homeassistant/components/climacell/translations/en.json index a35be85d5b2..3e5cd436ba8 100644 --- a/homeassistant/components/climacell/translations/en.json +++ b/homeassistant/components/climacell/translations/en.json @@ -1,4 +1,24 @@ { + "config": { + "error": { + "cannot_connect": "Failed to connect", + "invalid_api_key": "Invalid API key", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "api_version": "API Version", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "description": "If Latitude and Longitude are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default." + } + } + }, "options": { "step": { "init": { @@ -9,5 +29,6 @@ "title": "Update ClimaCell Options" } } - } + }, + "title": "ClimaCell" } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/fr.json b/homeassistant/components/climacell/translations/fr.json index 19b986a3db7..9194073bb5a 100644 --- a/homeassistant/components/climacell/translations/fr.json +++ b/homeassistant/components/climacell/translations/fr.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "timestep": "Min. Entre les pr\u00e9visions NowCast" + "timestep": "Min. entre les pr\u00e9visions NowCast" }, - "description": "Si vous choisissez d'activer l'entit\u00e9 de pr\u00e9vision \u00abnowcast\u00bb, vous pouvez configurer le nombre de minutes entre chaque pr\u00e9vision. Le nombre de pr\u00e9visions fournies d\u00e9pend du nombre de minutes choisies entre les pr\u00e9visions.", + "description": "Si vous choisissez d'activer l'entit\u00e9 de pr\u00e9vision \u00ab\u00a0nowcast\u00a0\u00bb, vous pouvez configurer le nombre de minutes entre chaque pr\u00e9vision. Le nombre de pr\u00e9visions fournies d\u00e9pend du nombre de minutes choisies entre les pr\u00e9visions.", "title": "Mettre \u00e0 jour les options ClimaCell" } } diff --git a/homeassistant/components/climacell/translations/hu.json b/homeassistant/components/climacell/translations/hu.json index 3454a489455..bdeb913275c 100644 --- a/homeassistant/components/climacell/translations/hu.json +++ b/homeassistant/components/climacell/translations/hu.json @@ -3,7 +3,7 @@ "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", - "rate_limited": "Jelenleg korl\u00e1tozott sebess\u00e9g\u0171, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg k\u00e9s\u0151bb \u00fajra.", + "rate_limited": "Jelenleg korl\u00e1tozott a hozz\u00e1f\u00e9r\u00e9s, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg k\u00e9s\u0151bb \u00fajra.", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { @@ -13,7 +13,7 @@ "api_version": "API Verzi\u00f3", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Ha a Sz\u00e9less\u00e9g \u00e9s Hossz\u00fas\u00e1g nincs megadva, akkor a Home Assistant konfigur\u00e1ci\u00f3j\u00e1ban l\u00e9v\u0151 alap\u00e9rtelmezett \u00e9rt\u00e9keket fogjuk haszn\u00e1lni. Minden el\u0151rejelz\u00e9si t\u00edpushoz l\u00e9trej\u00f6n egy entit\u00e1s, de alap\u00e9rtelmez\u00e9s szerint csak az \u00d6n \u00e1ltal kiv\u00e1lasztottak lesznek enged\u00e9lyezve." } @@ -26,7 +26,7 @@ "timestep": "Min. A NowCast el\u0151rejelz\u00e9sek k\u00f6z\u00f6tt" }, "description": "Ha a `nowcast` el\u0151rejelz\u00e9si entit\u00e1s enged\u00e9lyez\u00e9s\u00e9t v\u00e1lasztja, be\u00e1ll\u00edthatja az egyes el\u0151rejelz\u00e9sek k\u00f6z\u00f6tti percek sz\u00e1m\u00e1t. A megadott el\u0151rejelz\u00e9sek sz\u00e1ma az el\u0151rejelz\u00e9sek k\u00f6z\u00f6tt kiv\u00e1lasztott percek sz\u00e1m\u00e1t\u00f3l f\u00fcgg.", - "title": "Friss\u00edtse a ClimaCell be\u00e1ll\u00edt\u00e1sokat" + "title": "ClimaCell be\u00e1ll\u00edt\u00e1sok friss\u00edt\u00e9se" } } }, diff --git a/homeassistant/components/climacell/translations/pl.json b/homeassistant/components/climacell/translations/pl.json index dc08cb9b1bf..e107be1e001 100644 --- a/homeassistant/components/climacell/translations/pl.json +++ b/homeassistant/components/climacell/translations/pl.json @@ -23,7 +23,7 @@ "step": { "init": { "data": { - "timestep": "Minuty pomi\u0119dzy prognozami" + "timestep": "Czas (min) mi\u0119dzy prognozami NowCast" }, "description": "Je\u015bli zdecydujesz si\u0119 w\u0142\u0105czy\u0107 encj\u0119 prognozy \u201enowcast\u201d, mo\u017cesz skonfigurowa\u0107 liczb\u0119 minut mi\u0119dzy ka\u017cd\u0105 prognoz\u0105. Liczba dostarczonych prognoz zale\u017cy od liczby minut wybranych mi\u0119dzy prognozami.", "title": "Opcje aktualizacji ClimaCell" diff --git a/homeassistant/components/climacell/translations/sensor.fr.json b/homeassistant/components/climacell/translations/sensor.fr.json index 95625c2b8da..acff91fc570 100644 --- a/homeassistant/components/climacell/translations/sensor.fr.json +++ b/homeassistant/components/climacell/translations/sensor.fr.json @@ -2,7 +2,7 @@ "state": { "climacell__health_concern": { "good": "Bon", - "hazardous": "Hasardeux", + "hazardous": "Dangereux", "moderate": "Mod\u00e9r\u00e9", "unhealthy": "Mauvais pour la sant\u00e9", "unhealthy_for_sensitive_groups": "Mauvaise qualit\u00e9 pour les groupes sensibles", diff --git a/homeassistant/components/climacell/translations/sensor.hu.json b/homeassistant/components/climacell/translations/sensor.hu.json index d864d505143..656a460f429 100644 --- a/homeassistant/components/climacell/translations/sensor.hu.json +++ b/homeassistant/components/climacell/translations/sensor.hu.json @@ -17,7 +17,7 @@ "very_low": "Nagyon alacsony" }, "climacell__precipitation_type": { - "freezing_rain": "Fagyos es\u0151", + "freezing_rain": "Havas es\u0151", "ice_pellets": "\u00d3nos es\u0151", "none": "Nincs", "rain": "Es\u0151", diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index b076e0db01a..818caaaa78d 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -33,7 +33,7 @@ from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType from homeassistant.util.temperature import convert as convert_temperature -from .const import ( +from .const import ( # noqa: F401 ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, @@ -74,6 +74,9 @@ from .const import ( SUPPORT_TARGET_HUMIDITY, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) DEFAULT_MIN_TEMP = 7 @@ -98,7 +101,7 @@ SET_TEMPERATURE_SCHEMA = vol.All( vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float), - vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES), + vol.Optional(ATTR_HVAC_MODE): vol.Coerce(HVACMode), } ), ) @@ -115,44 +118,47 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") component.async_register_entity_service( SERVICE_SET_HVAC_MODE, - {vol.Required(ATTR_HVAC_MODE): vol.In(HVAC_MODES)}, + {vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)}, "async_set_hvac_mode", ) component.async_register_entity_service( SERVICE_SET_PRESET_MODE, {vol.Required(ATTR_PRESET_MODE): cv.string}, "async_set_preset_mode", - [SUPPORT_PRESET_MODE], + [ClimateEntityFeature.PRESET_MODE], ) component.async_register_entity_service( SERVICE_SET_AUX_HEAT, {vol.Required(ATTR_AUX_HEAT): cv.boolean}, async_service_aux_heat, - [SUPPORT_AUX_HEAT], + [ClimateEntityFeature.AUX_HEAT], ) component.async_register_entity_service( SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, async_service_temperature_set, - [SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE], + [ + ClimateEntityFeature.TARGET_TEMPERATURE, + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, + ], ) component.async_register_entity_service( SERVICE_SET_HUMIDITY, {vol.Required(ATTR_HUMIDITY): vol.Coerce(int)}, "async_set_humidity", - [SUPPORT_TARGET_HUMIDITY], + [ClimateEntityFeature.TARGET_HUMIDITY], ) component.async_register_entity_service( SERVICE_SET_FAN_MODE, {vol.Required(ATTR_FAN_MODE): cv.string}, "async_set_fan_mode", - [SUPPORT_FAN_MODE], + [ClimateEntityFeature.FAN_MODE], ) component.async_register_entity_service( SERVICE_SET_SWING_MODE, {vol.Required(ATTR_SWING_MODE): cv.string}, "async_set_swing_mode", - [SUPPORT_SWING_MODE], + [ClimateEntityFeature.SWING_MODE], ) return True @@ -183,9 +189,9 @@ class ClimateEntity(Entity): _attr_current_temperature: float | None = None _attr_fan_mode: str | None _attr_fan_modes: list[str] | None - _attr_hvac_action: str | None = None - _attr_hvac_mode: str - _attr_hvac_modes: list[str] + _attr_hvac_action: HVACAction | str | None = None + _attr_hvac_mode: HVACMode | str | None + _attr_hvac_modes: list[HVACMode] | list[str] _attr_is_aux_heat: bool | None _attr_max_humidity: int = DEFAULT_MAX_HUMIDITY _attr_max_temp: float @@ -204,10 +210,15 @@ class ClimateEntity(Entity): _attr_target_temperature: float | None = None _attr_temperature_unit: str + @final @property - def state(self) -> str: + def state(self) -> str | None: """Return the current state.""" - return self.hvac_mode + if self.hvac_mode is None: + return None + if not isinstance(self.hvac_mode, HVACMode): + return HVACMode(self.hvac_mode).value + return self.hvac_mode.value @property def precision(self) -> float: @@ -222,7 +233,7 @@ class ClimateEntity(Entity): def capability_attributes(self) -> dict[str, Any] | None: """Return the capability attributes.""" supported_features = self.supported_features - data = { + data: dict[str, Any] = { ATTR_HVAC_MODES: self.hvac_modes, ATTR_MIN_TEMP: show_temp( self.hass, self.min_temp, self.temperature_unit, self.precision @@ -235,17 +246,17 @@ class ClimateEntity(Entity): if self.target_temperature_step: data[ATTR_TARGET_TEMP_STEP] = self.target_temperature_step - if supported_features & SUPPORT_TARGET_HUMIDITY: + if supported_features & ClimateEntityFeature.TARGET_HUMIDITY: data[ATTR_MIN_HUMIDITY] = self.min_humidity data[ATTR_MAX_HUMIDITY] = self.max_humidity - if supported_features & SUPPORT_FAN_MODE: + if supported_features & ClimateEntityFeature.FAN_MODE: data[ATTR_FAN_MODES] = self.fan_modes - if supported_features & SUPPORT_PRESET_MODE: + if supported_features & ClimateEntityFeature.PRESET_MODE: data[ATTR_PRESET_MODES] = self.preset_modes - if supported_features & SUPPORT_SWING_MODE: + if supported_features & ClimateEntityFeature.SWING_MODE: data[ATTR_SWING_MODES] = self.swing_modes return data @@ -264,7 +275,7 @@ class ClimateEntity(Entity): ), } - if supported_features & SUPPORT_TARGET_TEMPERATURE: + if supported_features & ClimateEntityFeature.TARGET_TEMPERATURE: data[ATTR_TEMPERATURE] = show_temp( self.hass, self.target_temperature, @@ -272,7 +283,7 @@ class ClimateEntity(Entity): self.precision, ) - if supported_features & SUPPORT_TARGET_TEMPERATURE_RANGE: + if supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE: data[ATTR_TARGET_TEMP_HIGH] = show_temp( self.hass, self.target_temperature_high, @@ -289,22 +300,22 @@ class ClimateEntity(Entity): if self.current_humidity is not None: data[ATTR_CURRENT_HUMIDITY] = self.current_humidity - if supported_features & SUPPORT_TARGET_HUMIDITY: + if supported_features & ClimateEntityFeature.TARGET_HUMIDITY: data[ATTR_HUMIDITY] = self.target_humidity - if supported_features & SUPPORT_FAN_MODE: + if supported_features & ClimateEntityFeature.FAN_MODE: data[ATTR_FAN_MODE] = self.fan_mode if self.hvac_action: data[ATTR_HVAC_ACTION] = self.hvac_action - if supported_features & SUPPORT_PRESET_MODE: + if supported_features & ClimateEntityFeature.PRESET_MODE: data[ATTR_PRESET_MODE] = self.preset_mode - if supported_features & SUPPORT_SWING_MODE: + if supported_features & ClimateEntityFeature.SWING_MODE: data[ATTR_SWING_MODE] = self.swing_mode - if supported_features & SUPPORT_AUX_HEAT: + if supported_features & ClimateEntityFeature.AUX_HEAT: data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF return data @@ -325,27 +336,18 @@ class ClimateEntity(Entity): return self._attr_target_humidity @property - def hvac_mode(self) -> str: - """Return hvac operation ie. heat, cool mode. - - Need to be one of HVAC_MODE_*. - """ + def hvac_mode(self) -> HVACMode | str | None: + """Return hvac operation ie. heat, cool mode.""" return self._attr_hvac_mode @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ + def hvac_modes(self) -> list[HVACMode] | list[str]: + """Return the list of available hvac operation modes.""" return self._attr_hvac_modes @property - def hvac_action(self) -> str | None: - """Return the current running hvac operation if supported. - - Need to be one of CURRENT_HVAC_*. - """ + def hvac_action(self) -> HVACAction | str | None: + """Return the current running hvac operation if supported.""" return self._attr_hvac_action @property @@ -367,7 +369,7 @@ class ClimateEntity(Entity): def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach. - Requires SUPPORT_TARGET_TEMPERATURE_RANGE. + Requires ClimateEntityFeature.TARGET_TEMPERATURE_RANGE. """ return self._attr_target_temperature_high @@ -375,7 +377,7 @@ class ClimateEntity(Entity): def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach. - Requires SUPPORT_TARGET_TEMPERATURE_RANGE. + Requires ClimateEntityFeature.TARGET_TEMPERATURE_RANGE. """ return self._attr_target_temperature_low @@ -383,7 +385,7 @@ class ClimateEntity(Entity): def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ return self._attr_preset_mode @@ -391,7 +393,7 @@ class ClimateEntity(Entity): def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ return self._attr_preset_modes @@ -399,7 +401,7 @@ class ClimateEntity(Entity): def is_aux_heat(self) -> bool | None: """Return true if aux heater. - Requires SUPPORT_AUX_HEAT. + Requires ClimateEntityFeature.AUX_HEAT. """ return self._attr_is_aux_heat @@ -407,7 +409,7 @@ class ClimateEntity(Entity): def fan_mode(self) -> str | None: """Return the fan setting. - Requires SUPPORT_FAN_MODE. + Requires ClimateEntityFeature.FAN_MODE. """ return self._attr_fan_mode @@ -415,7 +417,7 @@ class ClimateEntity(Entity): def fan_modes(self) -> list[str] | None: """Return the list of available fan modes. - Requires SUPPORT_FAN_MODE. + Requires ClimateEntityFeature.FAN_MODE. """ return self._attr_fan_modes @@ -423,7 +425,7 @@ class ClimateEntity(Entity): def swing_mode(self) -> str | None: """Return the swing setting. - Requires SUPPORT_SWING_MODE. + Requires ClimateEntityFeature.SWING_MODE. """ return self._attr_swing_mode @@ -431,7 +433,7 @@ class ClimateEntity(Entity): def swing_modes(self) -> list[str] | None: """Return the list of available swing modes. - Requires SUPPORT_SWING_MODE. + Requires ClimateEntityFeature.SWING_MODE. """ return self._attr_swing_modes @@ -461,11 +463,11 @@ class ClimateEntity(Entity): """Set new target fan mode.""" await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode) - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" raise NotImplementedError() - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode) @@ -508,7 +510,7 @@ class ClimateEntity(Entity): return # Fake turn on - for mode in (HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_COOL): + for mode in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL): if mode not in self.hvac_modes: continue await self.async_set_hvac_mode(mode) @@ -521,8 +523,8 @@ class ClimateEntity(Entity): return # Fake turn off - if HVAC_MODE_OFF in self.hvac_modes: - await self.async_set_hvac_mode(HVAC_MODE_OFF) + if HVACMode.OFF in self.hvac_modes: + await self.async_set_hvac_mode(HVACMode.OFF) @property def supported_features(self) -> int: diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index 773ee5920da..202da1af597 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -1,36 +1,46 @@ """Provides the constants needed for component.""" -# All activity disabled / Device is off/standby +from enum import IntEnum + +from homeassistant.backports.enum import StrEnum + + +class HVACMode(StrEnum): + """HVAC mode for climate devices.""" + + # All activity disabled / Device is off/standby + OFF = "off" + + # Heating + HEAT = "heat" + + # Cooling + COOL = "cool" + + # The device supports heating/cooling to a range + HEAT_COOL = "heat_cool" + + # The temperature is set based on a schedule, learned behavior, AI or some + # other related mechanism. User is not able to adjust the temperature + AUTO = "auto" + + # Device is in Dry/Humidity mode + DRY = "dry" + + # Only the fan is on, not fan and another mode like cool + FAN_ONLY = "fan_only" + + +# These HVAC_MODE_* constants are deprecated as of Home Assistant 2022.5. +# Please use the HVACMode enum instead. HVAC_MODE_OFF = "off" - -# Heating HVAC_MODE_HEAT = "heat" - -# Cooling HVAC_MODE_COOL = "cool" - -# The device supports heating/cooling to a range HVAC_MODE_HEAT_COOL = "heat_cool" - -# The temperature is set based on a schedule, learned behavior, AI or some -# other related mechanism. User is not able to adjust the temperature HVAC_MODE_AUTO = "auto" - -# Device is in Dry/Humidity mode HVAC_MODE_DRY = "dry" - -# Only the fan is on, not fan and another mode like cool HVAC_MODE_FAN_ONLY = "fan_only" - -HVAC_MODES = [ - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_AUTO, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, -] +HVAC_MODES = [cls.value for cls in HVACMode] # No preset is active PRESET_NONE = "none" @@ -77,24 +87,26 @@ SWING_VERTICAL = "vertical" SWING_HORIZONTAL = "horizontal" -# This are support current states of HVAC +class HVACAction(StrEnum): + """HVAC action for climate devices.""" + + COOLING = "cooling" + DRYING = "drying" + FAN = "fan" + HEATING = "heating" + IDLE = "idle" + OFF = "off" + + +# These CURRENT_HVAC_* constants are deprecated as of Home Assistant 2022.5. +# Please use the HVACAction enum instead. CURRENT_HVAC_OFF = "off" CURRENT_HVAC_HEAT = "heating" CURRENT_HVAC_COOL = "cooling" CURRENT_HVAC_DRY = "drying" CURRENT_HVAC_IDLE = "idle" CURRENT_HVAC_FAN = "fan" - - -# A list of possible HVAC actions. -CURRENT_HVAC_ACTIONS = [ - CURRENT_HVAC_OFF, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_FAN, -] +CURRENT_HVAC_ACTIONS = [cls.value for cls in HVACAction] ATTR_AUX_HEAT = "aux_heat" @@ -133,6 +145,21 @@ SERVICE_SET_HVAC_MODE = "set_hvac_mode" SERVICE_SET_SWING_MODE = "set_swing_mode" SERVICE_SET_TEMPERATURE = "set_temperature" + +class ClimateEntityFeature(IntEnum): + """Supported features of the climate entity.""" + + TARGET_TEMPERATURE = 1 + TARGET_TEMPERATURE_RANGE = 2 + TARGET_HUMIDITY = 4 + FAN_MODE = 8 + PRESET_MODE = 16 + SWING_MODE = 32 + AUX_HEAT = 64 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the ClimateEntityFeature enum instead. SUPPORT_TARGET_TEMPERATURE = 1 SUPPORT_TARGET_TEMPERATURE_RANGE = 2 SUPPORT_TARGET_HUMIDITY = 4 diff --git a/homeassistant/components/climate/group.py b/homeassistant/components/climate/group.py index 3603e37d970..cec41f81b28 100644 --- a/homeassistant/components/climate/group.py +++ b/homeassistant/components/climate/group.py @@ -5,7 +5,7 @@ from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, callback -from .const import HVAC_MODE_OFF, HVAC_MODES +from .const import HVAC_MODES, HVACMode @callback @@ -14,6 +14,6 @@ def async_describe_on_off_states( ) -> None: """Describe group on off states.""" registry.on_off_states( - set(HVAC_MODES) - {HVAC_MODE_OFF}, + set(HVAC_MODES) - {HVACMode.OFF}, STATE_OFF, ) diff --git a/homeassistant/components/climate/translations/et.json b/homeassistant/components/climate/translations/et.json index 7be57f4cdaa..c57a4d72991 100644 --- a/homeassistant/components/climate/translations/et.json +++ b/homeassistant/components/climate/translations/et.json @@ -18,8 +18,8 @@ "_": { "auto": "Automaatne", "cool": "Jahuta", - "dry": "Kuiv", - "fan_only": "Ainult ventilaator", + "dry": "Kuivata", + "fan_only": "Ventileeri", "heat": "Soojenda", "heat_cool": "K\u00fcta/jahuta", "off": "V\u00e4ljas" diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index ad34186b7df..c47544f9d99 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -118,14 +118,15 @@ class CloudClient(Interface): cloud_user = await self._prefs.get_cloud_user() - self._google_config = google_config.CloudGoogleConfig( + google_conf = google_config.CloudGoogleConfig( self._hass, self.google_user_config, cloud_user, self._prefs, self.cloud, ) - await self._google_config.async_initialize() + await google_conf.async_initialize() + self._google_config = google_conf return self._google_config diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index e2b21ffc56d..8f190103e87 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -9,8 +9,8 @@ from hass_nabucasa.google_report_state import ErrorResponse from homeassistant.components.google_assistant.const import DOMAIN as GOOGLE_DOMAIN from homeassistant.components.google_assistant.helpers import AbstractConfig from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES -from homeassistant.core import CoreState, split_entity_id -from homeassistant.helpers import entity_registry as er, start +from homeassistant.core import CoreState, Event, callback, split_entity_id +from homeassistant.helpers import device_registry as dr, entity_registry as er, start from homeassistant.setup import async_setup_component from .const import ( @@ -103,6 +103,10 @@ class CloudGoogleConfig(AbstractConfig): er.EVENT_ENTITY_REGISTRY_UPDATED, self._handle_entity_registry_updated, ) + self.hass.bus.async_listen( + dr.EVENT_DEVICE_REGISTRY_UPDATED, + self._handle_device_registry_updated, + ) def should_expose(self, state): """If a state object should be exposed.""" @@ -217,9 +221,14 @@ class CloudGoogleConfig(AbstractConfig): self._cur_entity_prefs = prefs.google_entity_configs self._cur_default_expose = prefs.google_default_expose - async def _handle_entity_registry_updated(self, event): + @callback + def _handle_entity_registry_updated(self, event: Event) -> None: """Handle when entity registry updated.""" - if not self.enabled or not self._cloud.is_logged_in: + if ( + not self.enabled + or not self._cloud.is_logged_in + or self.hass.state != CoreState.running + ): return # Only consider entity registry updates if info relevant for Google has changed @@ -233,7 +242,30 @@ class CloudGoogleConfig(AbstractConfig): if not self._should_expose_entity_id(entity_id): return - if self.hass.state != CoreState.running: + self.async_schedule_google_sync_all() + + @callback + def _handle_device_registry_updated(self, event: Event) -> None: + """Handle when device registry updated.""" + if ( + not self.enabled + or not self._cloud.is_logged_in + or self.hass.state != CoreState.running + ): + return + + # Device registry is only used for area changes. All other changes are ignored. + if event.data["action"] != "update" or "area_id" not in event.data["changes"]: + return + + # Check if any exposed entity uses the device area + if not any( + entity_entry.area_id is None + and self._should_expose_entity_id(entity_entry.entity_id) + for entity_entry in er.async_entries_for_device( + er.async_get(self.hass), event.data["device_id"] + ) + ): return self.async_schedule_google_sync_all() diff --git a/homeassistant/components/cloudflare/translations/he.json b/homeassistant/components/cloudflare/translations/he.json index 1f53e94240c..9303ea9143c 100644 --- a/homeassistant/components/cloudflare/translations/he.json +++ b/homeassistant/components/cloudflare/translations/he.json @@ -25,7 +25,8 @@ "user": { "data": { "api_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API" - } + }, + "description": "\u05e9\u05d9\u05dc\u05d5\u05d1 \u05d6\u05d4 \u05d3\u05d5\u05e8\u05e9 \u05d0\u05e1\u05d9\u05de\u05d5\u05df API \u05e9\u05e0\u05d5\u05e6\u05e8 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea Zone:Zone:Read and Zone:DNS:\u05e2\u05e8\u05d9\u05db\u05ea \u05d4\u05e8\u05e9\u05d0\u05d5\u05ea \u05e2\u05d1\u05d5\u05e8 \u05db\u05dc \u05d4\u05d0\u05d6\u05d5\u05e8\u05d9\u05dd \u05d1\u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05dc\u05da." }, "zone": { "data": { diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index 199d2c089c7..d80b4f6ffb1 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -6,19 +6,14 @@ import logging from pycmus import exceptions, remote import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_SET, ) from homeassistant.const import ( CONF_HOST, @@ -39,18 +34,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "cmus" DEFAULT_PORT = 3000 -SUPPORT_CMUS = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_SEEK - | SUPPORT_PLAY -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Inclusive(CONF_HOST, "remote"): cv.string, @@ -109,7 +92,17 @@ class CmusDevice(MediaPlayerEntity): """Representation of a running cmus.""" _attr_media_content_type = MEDIA_TYPE_MUSIC - _attr_supported_features = SUPPORT_CMUS + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.PLAY + ) def __init__(self, device, name, server): """Initialize the CMUS device.""" diff --git a/homeassistant/components/co2signal/__init__.py b/homeassistant/components/co2signal/__init__.py index 56be5bca57b..ba613ea6b18 100644 --- a/homeassistant/components/co2signal/__init__.py +++ b/homeassistant/components/co2signal/__init__.py @@ -1,9 +1,10 @@ """The CO2 Signal integration.""" from __future__ import annotations +from collections.abc import Mapping from datetime import timedelta import logging -from typing import TypedDict, cast +from typing import Any, TypedDict, cast import CO2Signal @@ -106,7 +107,7 @@ class UnknownError(CO2Error): """Raised when an unknown error occurs.""" -def get_data(hass: HomeAssistant, config: dict) -> CO2SignalResponse: +def get_data(hass: HomeAssistant, config: Mapping[str, Any]) -> CO2SignalResponse: """Get data from the API.""" if CONF_COUNTRY_CODE in config: latitude = None diff --git a/homeassistant/components/coinbase/translations/ca.json b/homeassistant/components/coinbase/translations/ca.json index 0772a04aa15..0543f97003a 100644 --- a/homeassistant/components/coinbase/translations/ca.json +++ b/homeassistant/components/coinbase/translations/ca.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Saldos de cartera a informar.", "exchange_base": "Moneda base per als sensors de canvi de tipus.", - "exchange_rate_currencies": "Tipus de canvi a informar." + "exchange_rate_currencies": "Tipus de canvi a informar.", + "exchnage_rate_precision": "Nombre de posicions decimals per als tipus de canvi." }, "description": "Ajusta les opcions de Coinbase" } diff --git a/homeassistant/components/coinbase/translations/de.json b/homeassistant/components/coinbase/translations/de.json index 4c1d2edc5ca..d4b58ae42bd 100644 --- a/homeassistant/components/coinbase/translations/de.json +++ b/homeassistant/components/coinbase/translations/de.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Zu meldende Wallet-Guthaben.", "exchange_base": "Basisw\u00e4hrung f\u00fcr Wechselkurssensoren.", - "exchange_rate_currencies": "Zu meldende Wechselkurse." + "exchange_rate_currencies": "Zu meldende Wechselkurse.", + "exchnage_rate_precision": "Anzahl der Dezimalstellen f\u00fcr Wechselkurse." }, "description": "Coinbase-Optionen anpassen" } diff --git a/homeassistant/components/coinbase/translations/el.json b/homeassistant/components/coinbase/translations/el.json index 196aaad8643..05d6a1f7415 100644 --- a/homeassistant/components/coinbase/translations/el.json +++ b/homeassistant/components/coinbase/translations/el.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "\u03a5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1 \u03c0\u03bf\u03c1\u03c4\u03bf\u03c6\u03bf\u03bb\u03b9\u03bf\u03cd \u03c0\u03c1\u03bf\u03c2 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac.", "exchange_base": "\u0392\u03b1\u03c3\u03b9\u03ba\u03cc \u03bd\u03cc\u03bc\u03b9\u03c3\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ce\u03bd \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03b9\u03ce\u03bd.", - "exchange_rate_currencies": "\u03a3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac." + "exchange_rate_currencies": "\u03a3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2 \u03c0\u03c1\u03bf\u03c2 \u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac.", + "exchnage_rate_precision": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03b3\u03b9\u03b1 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03bc\u03b1\u03c4\u03b9\u03ba\u03ad\u03c2 \u03b9\u03c3\u03bf\u03c4\u03b9\u03bc\u03af\u03b5\u03c2." }, "description": "\u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd Coinbase" } diff --git a/homeassistant/components/coinbase/translations/en.json b/homeassistant/components/coinbase/translations/en.json index 019159c8057..d5d7483e260 100644 --- a/homeassistant/components/coinbase/translations/en.json +++ b/homeassistant/components/coinbase/translations/en.json @@ -14,7 +14,9 @@ "user": { "data": { "api_key": "API Key", - "api_token": "API Secret" + "api_token": "API Secret", + "currencies": "Account Balance Currencies", + "exchange_rates": "Exchange Rates" }, "description": "Please enter the details of your API key as provided by Coinbase.", "title": "Coinbase API Key Details" @@ -24,7 +26,9 @@ "options": { "error": { "currency_unavailable": "One or more of the requested currency balances is not provided by your Coinbase API.", + "currency_unavaliable": "One or more of the requested currency balances is not provided by your Coinbase API.", "exchange_rate_unavailable": "One or more of the requested exchange rates is not provided by Coinbase.", + "exchange_rate_unavaliable": "One or more of the requested exchange rates is not provided by Coinbase.", "unknown": "Unexpected error" }, "step": { diff --git a/homeassistant/components/coinbase/translations/et.json b/homeassistant/components/coinbase/translations/et.json index 0c30ec9ff34..821d17656d2 100644 --- a/homeassistant/components/coinbase/translations/et.json +++ b/homeassistant/components/coinbase/translations/et.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Rahakoti saldod teavitamine.", "exchange_base": "Vahetuskursiandurite baasvaluuta.", - "exchange_rate_currencies": "Vahetuskursside aruanne." + "exchange_rate_currencies": "Vahetuskursside aruanne.", + "exchnage_rate_precision": "Vahetuskursside k\u00fcmnendkohtade arv." }, "description": "Kohanda Coinbase'i valikuid" } diff --git a/homeassistant/components/coinbase/translations/fr.json b/homeassistant/components/coinbase/translations/fr.json index 01cd7ec6dbf..77664cca73d 100644 --- a/homeassistant/components/coinbase/translations/fr.json +++ b/homeassistant/components/coinbase/translations/fr.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Soldes du portefeuille \u00e0 d\u00e9clarer.", "exchange_base": "Devise de base pour les capteurs de taux de change.", - "exchange_rate_currencies": "Taux de change \u00e0 d\u00e9clarer." + "exchange_rate_currencies": "Taux de change \u00e0 d\u00e9clarer.", + "exchnage_rate_precision": "Nombre de d\u00e9cimales pour les taux de change." }, "description": "Ajuster les options de Coinbase" } diff --git a/homeassistant/components/coinbase/translations/hu.json b/homeassistant/components/coinbase/translations/hu.json index 3db29058e68..44287da0ee6 100644 --- a/homeassistant/components/coinbase/translations/hu.json +++ b/homeassistant/components/coinbase/translations/hu.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Jelentend\u0151 p\u00e9nzt\u00e1rca egyenlegek.", "exchange_base": "Az \u00e1rfolyam-\u00e9rz\u00e9kel\u0151k alapvalut\u00e1ja.", - "exchange_rate_currencies": "Jelentend\u0151 \u00e1rfolyamok." + "exchange_rate_currencies": "Jelentend\u0151 \u00e1rfolyamok.", + "exchnage_rate_precision": "A tizedesjegyek sz\u00e1ma az \u00e1tv\u00e1lt\u00e1si \u00e1rfolyamokn\u00e1l." }, "description": "\u00c1ll\u00edtsa be a Coinbase opci\u00f3kat" } diff --git a/homeassistant/components/coinbase/translations/id.json b/homeassistant/components/coinbase/translations/id.json index 1d431ffd2fd..477aceafa45 100644 --- a/homeassistant/components/coinbase/translations/id.json +++ b/homeassistant/components/coinbase/translations/id.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Saldo dompet untuk dilaporkan.", "exchange_base": "Mata uang dasar untuk sensor nilai tukar.", - "exchange_rate_currencies": "Nilai tukar untuk dilaporkan." + "exchange_rate_currencies": "Nilai tukar untuk dilaporkan.", + "exchnage_rate_precision": "Jumlah digit desimal untuk nilai tukar." }, "description": "Sesuaikan Opsi Coinbase" } diff --git a/homeassistant/components/coinbase/translations/it.json b/homeassistant/components/coinbase/translations/it.json index ecc0d93e747..64b5b0cdca7 100644 --- a/homeassistant/components/coinbase/translations/it.json +++ b/homeassistant/components/coinbase/translations/it.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Saldi del portafoglio da segnalare.", "exchange_base": "Valuta di base per i sensori di tasso di cambio.", - "exchange_rate_currencies": "Tassi di cambio da segnalare." + "exchange_rate_currencies": "Tassi di cambio da segnalare.", + "exchnage_rate_precision": "Numero di cifre decimali per i tassi di cambio." }, "description": "Regola le opzioni di Coinbase" } diff --git a/homeassistant/components/coinbase/translations/ja.json b/homeassistant/components/coinbase/translations/ja.json index 6b6791d40b2..321e0c05d9d 100644 --- a/homeassistant/components/coinbase/translations/ja.json +++ b/homeassistant/components/coinbase/translations/ja.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "\u30a6\u30a9\u30ec\u30c3\u30c8\u306e\u6b8b\u9ad8\u3092\u5831\u544a\u3059\u308b\u3002", "exchange_base": "\u70ba\u66ff\u30ec\u30fc\u30c8\u30bb\u30f3\u30b5\u30fc\u306e\u57fa\u6e96\u901a\u8ca8\u3002", - "exchange_rate_currencies": "\u30ec\u30dd\u30fc\u30c8\u3059\u3079\u304d\u70ba\u66ff\u30ec\u30fc\u30c8" + "exchange_rate_currencies": "\u30ec\u30dd\u30fc\u30c8\u3059\u3079\u304d\u70ba\u66ff\u30ec\u30fc\u30c8", + "exchnage_rate_precision": "\u70ba\u66ff\u30ec\u30fc\u30c8\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3002" }, "description": "Coinbase\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u8abf\u6574" } diff --git a/homeassistant/components/coinbase/translations/nl.json b/homeassistant/components/coinbase/translations/nl.json index fc2068cb01a..98763deb9a7 100644 --- a/homeassistant/components/coinbase/translations/nl.json +++ b/homeassistant/components/coinbase/translations/nl.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Wallet-saldi om te rapporteren.", "exchange_base": "Basisvaluta voor wisselkoerssensoren.", - "exchange_rate_currencies": "Wisselkoersen om te rapporteren." + "exchange_rate_currencies": "Wisselkoersen om te rapporteren.", + "exchnage_rate_precision": "Aantal decimalen voor wisselkoersen." }, "description": "Coinbase-opties aanpassen" } diff --git a/homeassistant/components/coinbase/translations/no.json b/homeassistant/components/coinbase/translations/no.json index 9a24d984c32..5171814cf9d 100644 --- a/homeassistant/components/coinbase/translations/no.json +++ b/homeassistant/components/coinbase/translations/no.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Lommeboksaldoer som skal rapporteres.", "exchange_base": "Standardvaluta for valutakurssensorer.", - "exchange_rate_currencies": "Valutakurser som skal rapporteres." + "exchange_rate_currencies": "Valutakurser som skal rapporteres.", + "exchnage_rate_precision": "Antall desimaler for valutakurser." }, "description": "Juster Coinbase-alternativer" } diff --git a/homeassistant/components/coinbase/translations/pl.json b/homeassistant/components/coinbase/translations/pl.json index 14432be5928..7465ae24486 100644 --- a/homeassistant/components/coinbase/translations/pl.json +++ b/homeassistant/components/coinbase/translations/pl.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Salda portfela do zg\u0142oszenia.", "exchange_base": "Waluta bazowa dla sensor\u00f3w kurs\u00f3w walut.", - "exchange_rate_currencies": "Kursy walut do zg\u0142oszenia." + "exchange_rate_currencies": "Kursy walut do zg\u0142oszenia.", + "exchnage_rate_precision": "Liczba miejsc po przecinku dla kurs\u00f3w walut." }, "description": "Dostosuj opcje Coinbase" } diff --git a/homeassistant/components/coinbase/translations/pt-BR.json b/homeassistant/components/coinbase/translations/pt-BR.json index 6596135208b..5ed52fa7afc 100644 --- a/homeassistant/components/coinbase/translations/pt-BR.json +++ b/homeassistant/components/coinbase/translations/pt-BR.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Saldos da carteira a relatar.", "exchange_base": "Moeda base para sensores de taxa de c\u00e2mbio.", - "exchange_rate_currencies": "Taxas de c\u00e2mbio a informar." + "exchange_rate_currencies": "Taxas de c\u00e2mbio a informar.", + "exchnage_rate_precision": "N\u00famero de casas decimais para taxas de c\u00e2mbio." }, "description": "Ajustar as op\u00e7\u00f5es da Coinbase" } diff --git a/homeassistant/components/coinbase/translations/ru.json b/homeassistant/components/coinbase/translations/ru.json index 41439aacea8..951c0182320 100644 --- a/homeassistant/components/coinbase/translations/ru.json +++ b/homeassistant/components/coinbase/translations/ru.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "\u0411\u0430\u043b\u0430\u043d\u0441\u044b \u043a\u043e\u0448\u0435\u043b\u044c\u043a\u0430 \u0434\u043b\u044f \u043e\u0442\u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438.", "exchange_base": "\u0411\u0430\u0437\u043e\u0432\u0430\u044f \u0432\u0430\u043b\u044e\u0442\u0430 \u0434\u043b\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u043e\u0431\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u0443\u0440\u0441\u0430.", - "exchange_rate_currencies": "\u041a\u0443\u0440\u0441\u044b \u0432\u0430\u043b\u044e\u0442 \u0434\u043b\u044f \u043e\u0442\u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438." + "exchange_rate_currencies": "\u041a\u0443\u0440\u0441\u044b \u0432\u0430\u043b\u044e\u0442 \u0434\u043b\u044f \u043e\u0442\u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438.", + "exchnage_rate_precision": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439 \u0434\u043b\u044f \u043e\u0431\u043c\u0435\u043d\u043d\u044b\u0445 \u043a\u0443\u0440\u0441\u043e\u0432." }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 Coinbase" } diff --git a/homeassistant/components/coinbase/translations/tr.json b/homeassistant/components/coinbase/translations/tr.json index e52b6a81e76..e21cab489e4 100644 --- a/homeassistant/components/coinbase/translations/tr.json +++ b/homeassistant/components/coinbase/translations/tr.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "Rapor edilecek c\u00fczdan bakiyeleri.", "exchange_base": "D\u00f6viz kuru sens\u00f6rleri i\u00e7in temel para birimi.", - "exchange_rate_currencies": "Raporlanacak d\u00f6viz kurlar\u0131." + "exchange_rate_currencies": "Raporlanacak d\u00f6viz kurlar\u0131.", + "exchnage_rate_precision": "D\u00f6viz kurlar\u0131 i\u00e7in ondal\u0131k basamak say\u0131s\u0131." }, "description": "Coinbase Se\u00e7eneklerini Ayarlay\u0131n" } diff --git a/homeassistant/components/coinbase/translations/zh-Hant.json b/homeassistant/components/coinbase/translations/zh-Hant.json index 4510515bff2..315fe90254f 100644 --- a/homeassistant/components/coinbase/translations/zh-Hant.json +++ b/homeassistant/components/coinbase/translations/zh-Hant.json @@ -36,7 +36,8 @@ "data": { "account_balance_currencies": "\u5e33\u6236\u9918\u984d\u56de\u5831\u503c\u3002", "exchange_base": "\u532f\u7387\u611f\u6e2c\u5668\u57fa\u6e96\u8ca8\u5e63\u3002", - "exchange_rate_currencies": "\u532f\u7387\u56de\u5831\u503c\u3002" + "exchange_rate_currencies": "\u532f\u7387\u56de\u5831\u503c\u3002", + "exchnage_rate_precision": "\u532f\u7387\u5c0f\u6578\u4f4d\u6578\u3002" }, "description": "\u8abf\u6574 Coinbase \u9078\u9805" } diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index b2845068dbe..84d82c170e1 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -13,7 +13,7 @@ from pycomfoconnect import ( SENSOR_FAN_SPEED_MODE, ) -from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -53,6 +53,7 @@ def setup_platform( class ComfoConnectFan(FanEntity): """Representation of the ComfoConnect fan platform.""" + _attr_supported_features = FanEntityFeature.SET_SPEED current_speed = None def __init__(self, ccb: ComfoConnectBridge) -> None: @@ -101,11 +102,6 @@ class ComfoConnectFan(FanEntity): """Return the icon to use in the frontend.""" return "mdi:air-conditioner" - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED - @property def percentage(self) -> int | None: """Return the current speed percentage.""" diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py index 15664ebfaf6..e8f21c46278 100644 --- a/homeassistant/components/concord232/alarm_control_panel.py +++ b/homeassistant/components/concord232/alarm_control_panel.py @@ -11,10 +11,7 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import ( PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, + AlarmControlPanelEntityFeature, ) from homeassistant.const import ( CONF_CODE, @@ -75,6 +72,11 @@ def setup_platform( class Concord232Alarm(alarm.AlarmControlPanelEntity): """Representation of the Concord232-based alarm panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + def __init__(self, url, name, code, mode): """Initialize the Concord232 alarm panel.""" @@ -94,18 +96,13 @@ class Concord232Alarm(alarm.AlarmControlPanelEntity): @property def code_format(self): """Return the characters if code is defined.""" - return alarm.FORMAT_NUMBER + return alarm.CodeFormat.NUMBER @property def state(self): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - def update(self): """Update values from API.""" try: diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index f1a2d9aab84..5a39b786e27 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -23,7 +23,7 @@ async def async_setup(hass): if action != ACTION_DELETE: return - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) entity_id = ent_reg.async_get_entity_id(DOMAIN, DOMAIN, config_key) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 64151c7d90d..0a093ee4574 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -160,7 +160,6 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): async def get(self, request): """Not implemented.""" - # pylint: disable=no-self-use raise aiohttp.web_exceptions.HTTPMethodNotAllowed("GET", ["POST"]) # pylint: disable=arguments-differ diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index 6523ff84158..862c8c46f4d 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -20,7 +20,7 @@ async def async_setup(hass): if action != ACTION_DELETE: return - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) entity_id = ent_reg.async_get_entity_id(DOMAIN, HA_DOMAIN, config_key) diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index 7adc766a1ab..45a7a6dc227 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -6,9 +6,9 @@ from homeassistant.components.script.config import ( ) from homeassistant.config import SCRIPT_CONFIG_PATH from homeassistant.const import SERVICE_RELOAD -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er -from . import EditKeyBasedConfigView +from . import ACTION_DELETE, EditKeyBasedConfigView async def async_setup(hass): @@ -18,6 +18,18 @@ async def async_setup(hass): """post_write_hook for Config View that reloads scripts.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) + if action != ACTION_DELETE: + return + + ent_reg = er.async_get(hass) + + entity_id = ent_reg.async_get_entity_id(DOMAIN, DOMAIN, config_key) + + if entity_id is None: + return + + ent_reg.async_remove(entity_id) + hass.http.register_view( EditScriptConfigView( DOMAIN, diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index 95c6233fc14..5de212d03a5 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -7,21 +7,14 @@ A callback has to be provided to `request_config` which will be called when the user has submitted configuration information. """ from contextlib import suppress +from datetime import datetime import functools as ft from typing import Any -from homeassistant.const import ( - ATTR_ENTITY_PICTURE, - ATTR_FRIENDLY_NAME, - EVENT_TIME_CHANGED, -) -from homeassistant.core import ( - Event, - HomeAssistant, - ServiceCall, - callback as async_callback, -) +from homeassistant.const import ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME +from homeassistant.core import HomeAssistant, ServiceCall, callback as async_callback from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe @@ -213,11 +206,11 @@ class Configurator: self.hass.states.async_set(entity_id, STATE_CONFIGURED) @async_callback - def deferred_remove(event: Event): + def deferred_remove(now: datetime): """Remove the request state.""" - self.hass.states.async_remove(entity_id, context=event.context) + self.hass.states.async_remove(entity_id) - self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove) + async_call_later(self.hass, 1, deferred_remove) async def async_handle_service_call(self, call: ServiceCall) -> None: """Handle a configure service call.""" diff --git a/homeassistant/components/control4/light.py b/homeassistant/components/control4/light.py index e2aa7ccd443..ded72944af7 100644 --- a/homeassistant/components/control4/light.py +++ b/homeassistant/components/control4/light.py @@ -11,9 +11,9 @@ from pyControl4.light import C4Light from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_SCAN_INTERVAL @@ -164,6 +164,12 @@ class Control4Light(Control4Entity, LightEntity): device_id, ) self._is_dimmer = is_dimmer + if is_dimmer: + self._attr_color_mode = ColorMode.BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + else: + self._attr_color_mode = ColorMode.ONOFF + self._attr_supported_color_modes = {ColorMode.ONOFF} def create_api_object(self): """Create a pyControl4 device object. @@ -188,7 +194,7 @@ class Control4Light(Control4Entity, LightEntity): def supported_features(self) -> int: """Flag supported features.""" if self._is_dimmer: - return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + return LightEntityFeature.TRANSITION return 0 async def async_turn_on(self, **kwargs) -> None: diff --git a/homeassistant/components/conversation/agent.py b/homeassistant/components/conversation/agent.py index 251058c7edd..56cf4aecdea 100644 --- a/homeassistant/components/conversation/agent.py +++ b/homeassistant/components/conversation/agent.py @@ -17,12 +17,10 @@ class AbstractConversationAgent(ABC): async def async_get_onboarding(self): """Get onboard data.""" - # pylint: disable=no-self-use return None async def async_set_onboarding(self, shown): """Set onboard data.""" - # pylint: disable=no-self-use return True @abstractmethod diff --git a/homeassistant/components/coolmaster/climate.py b/homeassistant/components/coolmaster/climate.py index de1652d6938..2f2ea58bd5b 100644 --- a/homeassistant/components/coolmaster/climate.py +++ b/homeassistant/components/coolmaster/climate.py @@ -2,16 +2,7 @@ import logging from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant, callback @@ -21,14 +12,12 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import CONF_SUPPORTED_MODES, DATA_COORDINATOR, DATA_INFO, DOMAIN -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - CM_TO_HA_STATE = { - "heat": HVAC_MODE_HEAT, - "cool": HVAC_MODE_COOL, - "auto": HVAC_MODE_HEAT_COOL, - "dry": HVAC_MODE_DRY, - "fan": HVAC_MODE_FAN_ONLY, + "heat": HVACMode.HEAT, + "cool": HVACMode.COOL, + "auto": HVACMode.HEAT_COOL, + "dry": HVACMode.DRY, + "fan": HVACMode.FAN_ONLY, } HA_STATE_TO_CM = {value: key for key, value in CM_TO_HA_STATE.items()} @@ -65,6 +54,10 @@ async def async_setup_entry( class CoolmasterClimate(CoordinatorEntity, ClimateEntity): """Representation of a coolmaster climate device.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) + def __init__(self, coordinator, unit_id, unit, supported_modes, info): """Initialize the climate device.""" super().__init__(coordinator) @@ -94,11 +87,6 @@ class CoolmasterClimate(CoordinatorEntity, ClimateEntity): """Return unique ID for this device.""" return self._unit_id - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - @property def name(self): """Return the name of the climate device.""" @@ -127,7 +115,7 @@ class CoolmasterClimate(CoordinatorEntity, ClimateEntity): """Return hvac target hvac state.""" mode = self._unit.mode if not self._unit.is_on: - return HVAC_MODE_OFF + return HVACMode.OFF return CM_TO_HA_STATE[mode] @@ -163,7 +151,7 @@ class CoolmasterClimate(CoordinatorEntity, ClimateEntity): """Set new operation mode.""" _LOGGER.debug("Setting operation mode of %s to %s", self.unique_id, hvac_mode) - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_turn_off() else: self._unit = await self._unit.set_mode(HA_STATE_TO_CM[hvac_mode]) diff --git a/homeassistant/components/coolmaster/config_flow.py b/homeassistant/components/coolmaster/config_flow.py index 6a5c517fc85..2c5592c156b 100644 --- a/homeassistant/components/coolmaster/config_flow.py +++ b/homeassistant/components/coolmaster/config_flow.py @@ -1,31 +1,46 @@ """Config flow to configure Coolmaster.""" +from __future__ import annotations + +from typing import Any from pycoolmasternet_async import CoolMasterNet import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.components.climate.const import HVACMode +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult -from .const import AVAILABLE_MODES, CONF_SUPPORTED_MODES, DEFAULT_PORT, DOMAIN +from .const import CONF_SUPPORTED_MODES, DEFAULT_PORT, DOMAIN + +AVAILABLE_MODES = [ + HVACMode.OFF.value, + HVACMode.HEAT.value, + HVACMode.COOL.value, + HVACMode.DRY.value, + HVACMode.HEAT_COOL.value, + HVACMode.FAN_ONLY.value, +] MODES_SCHEMA = {vol.Required(mode, default=True): bool for mode in AVAILABLE_MODES} DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str, **MODES_SCHEMA}) -async def _validate_connection(hass: core.HomeAssistant, host): +async def _validate_connection(host: str) -> bool: cool = CoolMasterNet(host, DEFAULT_PORT) units = await cool.status() return bool(units) -class CoolmasterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class CoolmasterConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a Coolmaster config flow.""" VERSION = 1 - @core.callback - def _async_get_entry(self, data): + @callback + def _async_get_entry(self, data: dict[str, Any]) -> FlowResult: supported_modes = [ key for (key, value) in data.items() if key in AVAILABLE_MODES and value ] @@ -38,7 +53,9 @@ class CoolmasterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA) @@ -48,7 +65,7 @@ class CoolmasterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): host = user_input[CONF_HOST] try: - result = await _validate_connection(self.hass, host) + result = await _validate_connection(host) if not result: errors["base"] = "no_units" except OSError: diff --git a/homeassistant/components/coolmaster/const.py b/homeassistant/components/coolmaster/const.py index c07cbe392ef..e5aa1f1b93d 100644 --- a/homeassistant/components/coolmaster/const.py +++ b/homeassistant/components/coolmaster/const.py @@ -1,14 +1,5 @@ """Constants for the Coolmaster integration.""" -from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, -) - DATA_INFO = "info" DATA_COORDINATOR = "coordinator" @@ -17,12 +8,3 @@ DOMAIN = "coolmaster" DEFAULT_PORT = 10102 CONF_SUPPORTED_MODES = "supported_modes" - -AVAILABLE_MODES = [ - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_FAN_ONLY, -] diff --git a/homeassistant/components/coronavirus/translations/hu.json b/homeassistant/components/coronavirus/translations/hu.json index 9b79c82a014..880990f35c0 100644 --- a/homeassistant/components/coronavirus/translations/hu.json +++ b/homeassistant/components/coronavirus/translations/hu.json @@ -9,7 +9,7 @@ "data": { "country": "Orsz\u00e1g" }, - "title": "V\u00e1lassz egy orsz\u00e1got a megfigyel\u00e9shez" + "title": "V\u00e1lasszon egy orsz\u00e1got a figyel\u00e9shez" } } } diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 44573c0a6da..aecca5a4029 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta +from enum import IntEnum import functools as ft import logging from typing import Any, final @@ -79,6 +80,22 @@ DEVICE_CLASS_SHADE = CoverDeviceClass.SHADE.value DEVICE_CLASS_SHUTTER = CoverDeviceClass.SHUTTER.value DEVICE_CLASS_WINDOW = CoverDeviceClass.WINDOW.value + +class CoverEntityFeature(IntEnum): + """Supported features of the cover entity.""" + + OPEN = 1 + CLOSE = 2 + SET_POSITION = 4 + STOP = 8 + OPEN_TILT = 16 + CLOSE_TILT = 32 + STOP_TILT = 64 + SET_TILT_POSITION = 128 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the CoverEntityFeature enum instead. SUPPORT_OPEN = 1 SUPPORT_CLOSE = 2 SUPPORT_SET_POSITION = 4 @@ -109,11 +126,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await component.async_setup(config) component.async_register_entity_service( - SERVICE_OPEN_COVER, {}, "async_open_cover", [SUPPORT_OPEN] + SERVICE_OPEN_COVER, {}, "async_open_cover", [CoverEntityFeature.OPEN] ) component.async_register_entity_service( - SERVICE_CLOSE_COVER, {}, "async_close_cover", [SUPPORT_CLOSE] + SERVICE_CLOSE_COVER, {}, "async_close_cover", [CoverEntityFeature.CLOSE] ) component.async_register_entity_service( @@ -124,27 +141,39 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) }, "async_set_cover_position", - [SUPPORT_SET_POSITION], + [CoverEntityFeature.SET_POSITION], ) component.async_register_entity_service( - SERVICE_STOP_COVER, {}, "async_stop_cover", [SUPPORT_STOP] + SERVICE_STOP_COVER, {}, "async_stop_cover", [CoverEntityFeature.STOP] ) component.async_register_entity_service( - SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_OPEN | SUPPORT_CLOSE] + SERVICE_TOGGLE, + {}, + "async_toggle", + [CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE], ) component.async_register_entity_service( - SERVICE_OPEN_COVER_TILT, {}, "async_open_cover_tilt", [SUPPORT_OPEN_TILT] + SERVICE_OPEN_COVER_TILT, + {}, + "async_open_cover_tilt", + [CoverEntityFeature.OPEN_TILT], ) component.async_register_entity_service( - SERVICE_CLOSE_COVER_TILT, {}, "async_close_cover_tilt", [SUPPORT_CLOSE_TILT] + SERVICE_CLOSE_COVER_TILT, + {}, + "async_close_cover_tilt", + [CoverEntityFeature.CLOSE_TILT], ) component.async_register_entity_service( - SERVICE_STOP_COVER_TILT, {}, "async_stop_cover_tilt", [SUPPORT_STOP_TILT] + SERVICE_STOP_COVER_TILT, + {}, + "async_stop_cover_tilt", + [CoverEntityFeature.STOP_TILT], ) component.async_register_entity_service( @@ -155,14 +184,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) }, "async_set_cover_tilt_position", - [SUPPORT_SET_TILT_POSITION], + [CoverEntityFeature.SET_TILT_POSITION], ) component.async_register_entity_service( SERVICE_TOGGLE_COVER_TILT, {}, "async_toggle_tilt", - [SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT], + [CoverEntityFeature.OPEN_TILT | CoverEntityFeature.CLOSE_TILT], ) return True @@ -262,17 +291,19 @@ class CoverEntity(Entity): if self._attr_supported_features is not None: return self._attr_supported_features - supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + supported_features = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP + ) if self.current_cover_position is not None: - supported_features |= SUPPORT_SET_POSITION + supported_features |= CoverEntityFeature.SET_POSITION if self.current_cover_tilt_position is not None: supported_features |= ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_STOP_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION ) return supported_features @@ -395,7 +426,7 @@ class CoverEntity(Entity): await self.async_close_cover_tilt(**kwargs) def _get_toggle_function(self, fns): - if SUPPORT_STOP | self.supported_features and ( + if CoverEntityFeature.STOP | self.supported_features and ( self.is_closing or self.is_opening ): return fns["stop"] diff --git a/homeassistant/components/cover/translations/he.json b/homeassistant/components/cover/translations/he.json index 66f6b9f3bbe..9e6fa77594f 100644 --- a/homeassistant/components/cover/translations/he.json +++ b/homeassistant/components/cover/translations/he.json @@ -10,7 +10,7 @@ "stop": "\u05e2\u05e6\u05d5\u05e8 {entity_name}" }, "condition_type": { - "is_closed": "{entity_name} \u05e1\u05d2\u05d5\u05e8", + "is_closed": "{entity_name} \u05e0\u05e1\u05d2\u05e8", "is_closing": "{entity_name} \u05e0\u05e1\u05d2\u05e8", "is_open": "{entity_name} \u05e4\u05ea\u05d5\u05d7", "is_opening": "{entity_name} \u05e0\u05e4\u05ea\u05d7", @@ -28,7 +28,7 @@ }, "state": { "_": { - "closed": "\u05e1\u05d2\u05d5\u05e8", + "closed": "\u05e0\u05e1\u05d2\u05e8", "closing": "\u05e1\u05d5\u05d2\u05e8", "open": "\u05e4\u05ea\u05d5\u05d7", "opening": "\u05e4\u05d5\u05ea\u05d7", diff --git a/homeassistant/components/cpuspeed/translations/bg.json b/homeassistant/components/cpuspeed/translations/bg.json index df41c1d7b0a..22f23d4428d 100644 --- a/homeassistant/components/cpuspeed/translations/bg.json +++ b/homeassistant/components/cpuspeed/translations/bg.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f.", "already_configured": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "step": { diff --git a/homeassistant/components/cpuspeed/translations/ca.json b/homeassistant/components/cpuspeed/translations/ca.json index 17e6295a5a9..79d3ccdf412 100644 --- a/homeassistant/components/cpuspeed/translations/ca.json +++ b/homeassistant/components/cpuspeed/translations/ca.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "already_configured": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "not_compatible": "No es pot obtenir la informaci\u00f3 de CPU, aquesta integraci\u00f3 no \u00e9s compatible amb el teu sistema" }, diff --git a/homeassistant/components/cpuspeed/translations/de.json b/homeassistant/components/cpuspeed/translations/de.json index f32ef08adba..60f5123a61c 100644 --- a/homeassistant/components/cpuspeed/translations/de.json +++ b/homeassistant/components/cpuspeed/translations/de.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "already_configured": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "not_compatible": "CPU-Informationen k\u00f6nnen nicht abgerufen werden, diese Integration ist nicht mit deinem System kompatibel" }, diff --git a/homeassistant/components/cpuspeed/translations/el.json b/homeassistant/components/cpuspeed/translations/el.json index 4c3541e2640..aac30ca2a82 100644 --- a/homeassistant/components/cpuspeed/translations/el.json +++ b/homeassistant/components/cpuspeed/translations/el.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "already_configured": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae.", "not_compatible": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd CPU, \u03b1\u03c5\u03c4\u03ae \u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bc\u03b2\u03b1\u03c4\u03ae \u03bc\u03b5 \u03c4\u03bf \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03ac \u03c3\u03b1\u03c2" }, diff --git a/homeassistant/components/cpuspeed/translations/en.json b/homeassistant/components/cpuspeed/translations/en.json index adcf047b382..d482e5d3d3d 100644 --- a/homeassistant/components/cpuspeed/translations/en.json +++ b/homeassistant/components/cpuspeed/translations/en.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Already configured. Only a single configuration possible.", "already_configured": "Already configured. Only a single configuration possible.", "not_compatible": "Unable to get CPU information, this integration is not compatible with your system" }, diff --git a/homeassistant/components/cpuspeed/translations/es.json b/homeassistant/components/cpuspeed/translations/es.json index 03b2a17dba3..87ff7b0c783 100644 --- a/homeassistant/components/cpuspeed/translations/es.json +++ b/homeassistant/components/cpuspeed/translations/es.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "already_configured": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "not_compatible": "No se puede obtener informaci\u00f3n de la CPU, esta integraci\u00f3n no es compatible con su sistema" }, diff --git a/homeassistant/components/cpuspeed/translations/et.json b/homeassistant/components/cpuspeed/translations/et.json index 037d1b73086..69805d26d58 100644 --- a/homeassistant/components/cpuspeed/translations/et.json +++ b/homeassistant/components/cpuspeed/translations/et.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Juba seadistatud. Lubatud on ainult \u00fcks sidumine.", "already_configured": "Juba seadistatud. Lubatud on ainult \u00fcks seadistamine.", "not_compatible": "CPU teavet ei saa hankida, see sidumine ei \u00fchildu s\u00fcsteemiga" }, diff --git a/homeassistant/components/cpuspeed/translations/fr.json b/homeassistant/components/cpuspeed/translations/fr.json index 5de49927c49..26428d40ef2 100644 --- a/homeassistant/components/cpuspeed/translations/fr.json +++ b/homeassistant/components/cpuspeed/translations/fr.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "already_configured": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "not_compatible": "Impossible d'obtenir les informations CPU, cette int\u00e9gration n'est pas compatible avec votre syst\u00e8me" }, diff --git a/homeassistant/components/cpuspeed/translations/he.json b/homeassistant/components/cpuspeed/translations/he.json index e38d585f2a4..9c1bb588a8f 100644 --- a/homeassistant/components/cpuspeed/translations/he.json +++ b/homeassistant/components/cpuspeed/translations/he.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "alread_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea.", "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." }, "step": { "user": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?", + "title": "\u05de\u05d4\u05d9\u05e8\u05d5\u05ea \u05de\u05e2\u05d1\u05d3" } } - } + }, + "title": "\u05de\u05d4\u05d9\u05e8\u05d5\u05ea \u05de\u05e2\u05d1\u05d3" } \ No newline at end of file diff --git a/homeassistant/components/cpuspeed/translations/hu.json b/homeassistant/components/cpuspeed/translations/hu.json index 9ab3aa355dd..400baa4e362 100644 --- a/homeassistant/components/cpuspeed/translations/hu.json +++ b/homeassistant/components/cpuspeed/translations/hu.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "already_configured": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "not_compatible": "Nem lehet CPU-inform\u00e1ci\u00f3kat lek\u00e9rni, ez az integr\u00e1ci\u00f3 nem kompatibilis a rendszer\u00e9vel." }, diff --git a/homeassistant/components/cpuspeed/translations/id.json b/homeassistant/components/cpuspeed/translations/id.json index ab3b16da4e9..e8a87d3707d 100644 --- a/homeassistant/components/cpuspeed/translations/id.json +++ b/homeassistant/components/cpuspeed/translations/id.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "already_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", "not_compatible": "Tidak dapat mendapatkan informasi CPU, integrasi ini tidak kompatibel dengan sistem Anda" }, diff --git a/homeassistant/components/cpuspeed/translations/it.json b/homeassistant/components/cpuspeed/translations/it.json index 803458e48d7..ff84b5bf5ad 100644 --- a/homeassistant/components/cpuspeed/translations/it.json +++ b/homeassistant/components/cpuspeed/translations/it.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "already_configured": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "not_compatible": "Impossibile ottenere informazioni sulla CPU, questa integrazione non \u00e8 compatibile con il tuo sistema" }, diff --git a/homeassistant/components/cpuspeed/translations/ja.json b/homeassistant/components/cpuspeed/translations/ja.json index d8e2fa85fb7..e2264b57dcb 100644 --- a/homeassistant/components/cpuspeed/translations/ja.json +++ b/homeassistant/components/cpuspeed/translations/ja.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "already_configured": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002", "not_compatible": "CPU\u60c5\u5831\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3002\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306f\u3001\u304a\u4f7f\u3044\u306e\u30b7\u30b9\u30c6\u30e0\u3068\u4e92\u63db\u6027\u304c\u3042\u308a\u307e\u305b\u3093\u3002" }, diff --git a/homeassistant/components/cpuspeed/translations/nl.json b/homeassistant/components/cpuspeed/translations/nl.json index b29d9c9ddf1..c3d09b0b8a6 100644 --- a/homeassistant/components/cpuspeed/translations/nl.json +++ b/homeassistant/components/cpuspeed/translations/nl.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", "already_configured": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", "not_compatible": "Kan geen CPU-informatie ophalen, deze integratie is niet compatibel met uw systeem" }, diff --git a/homeassistant/components/cpuspeed/translations/no.json b/homeassistant/components/cpuspeed/translations/no.json index 40d5ed1de01..9f3cb464424 100644 --- a/homeassistant/components/cpuspeed/translations/no.json +++ b/homeassistant/components/cpuspeed/translations/no.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "already_configured": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "not_compatible": "Kan ikke hente CPU-informasjon, denne integrasjonen er ikke kompatibel med systemet ditt" }, diff --git a/homeassistant/components/cpuspeed/translations/pl.json b/homeassistant/components/cpuspeed/translations/pl.json index 81c37098c29..be477365437 100644 --- a/homeassistant/components/cpuspeed/translations/pl.json +++ b/homeassistant/components/cpuspeed/translations/pl.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "already_configured": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "not_compatible": "Nie mo\u017cna uzyska\u0107 informacji o procesorze, ta integracja nie jest zgodna z twoim systemem" }, diff --git a/homeassistant/components/cpuspeed/translations/pt-BR.json b/homeassistant/components/cpuspeed/translations/pt-BR.json index f39ce9b4c9a..b21fcb00def 100644 --- a/homeassistant/components/cpuspeed/translations/pt-BR.json +++ b/homeassistant/components/cpuspeed/translations/pt-BR.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", "already_configured": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel.", "not_compatible": "N\u00e3o \u00e9 poss\u00edvel obter informa\u00e7\u00f5es da CPU, esta integra\u00e7\u00e3o n\u00e3o \u00e9 compat\u00edvel com seu sistema" }, diff --git a/homeassistant/components/cpuspeed/translations/ru.json b/homeassistant/components/cpuspeed/translations/ru.json index 9638a43f7f7..0a44b56086c 100644 --- a/homeassistant/components/cpuspeed/translations/ru.json +++ b/homeassistant/components/cpuspeed/translations/ru.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "not_compatible": "\u041d\u0435 \u0443\u0434\u0430\u0451\u0442\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u0435, \u044d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0435\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u0430 \u0441 \u0412\u0430\u0448\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439." }, diff --git a/homeassistant/components/cpuspeed/translations/tr.json b/homeassistant/components/cpuspeed/translations/tr.json index d4dd5a271e0..f5689f2f3b4 100644 --- a/homeassistant/components/cpuspeed/translations/tr.json +++ b/homeassistant/components/cpuspeed/translations/tr.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "already_configured": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", "not_compatible": "CPU bilgisi al\u0131nam\u0131yor, bu entegrasyon sisteminizle uyumlu de\u011fil" }, diff --git a/homeassistant/components/cpuspeed/translations/zh-Hans.json b/homeassistant/components/cpuspeed/translations/zh-Hans.json index 41130cbdd39..95f5766edc9 100644 --- a/homeassistant/components/cpuspeed/translations/zh-Hans.json +++ b/homeassistant/components/cpuspeed/translations/zh-Hans.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "\u5f53\u524d\u96c6\u6210\u5df2\u88ab\u914d\u7f6e\uff0c\u4ec5\u80fd\u53ea\u6709\u4e00\u4e2a\u914d\u7f6e", "already_configured": "\u5f53\u524d\u96c6\u6210\u5df2\u88ab\u914d\u7f6e\uff0c\u4ec5\u80fd\u53ea\u6709\u4e00\u4e2a\u914d\u7f6e", "not_compatible": "\u65e0\u6cd5\u83b7\u53d6 CPU \u4fe1\u606f\uff0c\u8be5\u96c6\u6210\u4e0e\u60a8\u7684\u7cfb\u7edf\u4e0d\u517c\u5bb9" }, diff --git a/homeassistant/components/cpuspeed/translations/zh-Hant.json b/homeassistant/components/cpuspeed/translations/zh-Hant.json index 4885aead70a..b88f1d1b1d4 100644 --- a/homeassistant/components/cpuspeed/translations/zh-Hant.json +++ b/homeassistant/components/cpuspeed/translations/zh-Hant.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "alread_configured": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "already_configured": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002", "not_compatible": "\u7121\u6cd5\u53d6\u5f97 CPU \u8cc7\u8a0a\uff0c\u9019\u500b\u63d2\u4ef6\u8207\u4f60\u7684\u7cfb\u7d71\u4e0d\u76f8\u5bb9" }, diff --git a/homeassistant/components/crownstone/light.py b/homeassistant/components/crownstone/light.py index ff647b2fc84..a046a39cd62 100644 --- a/homeassistant/components/crownstone/light.py +++ b/homeassistant/components/crownstone/light.py @@ -9,11 +9,7 @@ from crownstone_cloud.const import DIMMING_ABILITY from crownstone_cloud.exceptions import CrownstoneAbilityError from crownstone_uart import CrownstoneUart -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -98,11 +94,16 @@ class CrownstoneEntity(CrownstoneBaseEntity, LightEntity): return crownstone_state_to_hass(self.device.state) > 0 @property - def supported_features(self) -> int: - """Return the supported features of this Crownstone.""" + def color_mode(self) -> str: + """Return the color mode of the light.""" if self.device.abilities.get(DIMMING_ABILITY).is_enabled: - return SUPPORT_BRIGHTNESS - return 0 + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[str] | None: + """Flag supported color modes.""" + return {self.color_mode} async def async_added_to_hass(self) -> None: """Set up a listener when this entity is added to HA.""" diff --git a/homeassistant/components/crownstone/translations/ca.json b/homeassistant/components/crownstone/translations/ca.json index 9de845d87c6..deded7c6b71 100644 --- a/homeassistant/components/crownstone/translations/ca.json +++ b/homeassistant/components/crownstone/translations/ca.json @@ -56,13 +56,6 @@ "description": "Selecciona el port s\u00e8rie de l'adaptador USB Crownstone.\n\nBusca un dispositiu amb VID 10C4 i PID EA60.", "title": "Configuraci\u00f3 de l'adaptador USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "Ruta del dispositiu USB" - }, - "description": "Selecciona el port s\u00e8rie de l'adaptador USB Crownstone.\n\nBusca un dispositiu amb VID 10C4 i PID EA60.", - "title": "Configuraci\u00f3 de l'adaptador USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "Ruta del dispositiu USB" @@ -70,26 +63,12 @@ "description": "Introdueix manualment la ruta de l'adaptador USB Crownstone.", "title": "Ruta manual de l'adaptador USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Ruta del dispositiu USB" - }, - "description": "Introdueix manualment la ruta de l'adaptador USB Crownstone.", - "title": "Ruta manual de l'adaptador USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Esfera Crownstone" }, "description": "Selecciona la esfera Crownstone on es troba l'USB.", "title": "Esfera Crownstone USB" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Esfera Crownstone" - }, - "description": "Selecciona la esfera Crownstone on es troba l'USB.", - "title": "Esfera Crownstone USB" } } } diff --git a/homeassistant/components/crownstone/translations/cs.json b/homeassistant/components/crownstone/translations/cs.json index 1f14cdd8eff..b6c0cae1be2 100644 --- a/homeassistant/components/crownstone/translations/cs.json +++ b/homeassistant/components/crownstone/translations/cs.json @@ -28,20 +28,10 @@ "usb_path": "Cesta k USB za\u0159\u00edzen\u00ed" } }, - "usb_config_option": { - "data": { - "usb_path": "Cesta k USB za\u0159\u00edzen\u00ed" - } - }, "usb_manual_config": { "data": { "usb_manual_path": "Cesta k USB za\u0159\u00edzen\u00ed" } - }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Cesta k USB za\u0159\u00edzen\u00ed" - } } } } diff --git a/homeassistant/components/crownstone/translations/de.json b/homeassistant/components/crownstone/translations/de.json index a969d9b2999..054d6987c29 100644 --- a/homeassistant/components/crownstone/translations/de.json +++ b/homeassistant/components/crownstone/translations/de.json @@ -56,13 +56,6 @@ "description": "W\u00e4hle den seriellen Anschluss des Crownstone-USB-Dongles.\n\nSuche nach einem Ger\u00e4t mit VID 10C4 und PID EA60.", "title": "Crownstone USB-Dongle-Konfiguration" }, - "usb_config_option": { - "data": { - "usb_path": "USB-Ger\u00e4te-Pfad" - }, - "description": "W\u00e4hle den seriellen Anschluss des Crownstone-USB-Dongles.\n\nSuche nach einem Ger\u00e4t mit VID 10C4 und PID EA60.", - "title": "Crownstone USB-Dongle-Konfiguration" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB-Ger\u00e4te-Pfad" @@ -70,26 +63,12 @@ "description": "Gib den Pfad eines Crownstone USB-Dongles manuell ein.", "title": "Crownstone USB-Dongle manueller Pfad" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB-Ger\u00e4te-Pfad" - }, - "description": "Gib den Pfad eines Crownstone USB-Dongles manuell ein.", - "title": "Crownstone USB-Dongle manueller Pfad" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "W\u00e4hle eine Crownstone Sphere aus, in der sich der USB-Stick befindet.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "W\u00e4hle eine Crownstone Sphere aus, in der sich der USB-Stick befindet.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/el.json b/homeassistant/components/crownstone/translations/el.json index 81cfbd9ad39..b682dedcb1b 100644 --- a/homeassistant/components/crownstone/translations/el.json +++ b/homeassistant/components/crownstone/translations/el.json @@ -56,13 +56,6 @@ "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" }, - "usb_config_option": { - "data": { - "usb_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03c4\u03bf\u03c5 Crownstone USB dongle.\n\n\u0391\u03bd\u03b1\u03b6\u03b7\u03c4\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 VID 10C4 \u03ba\u03b1\u03b9 PID EA60.", - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 Crownstone USB dongle" - }, "usb_manual_config": { "data": { "usb_manual_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" @@ -70,26 +63,12 @@ "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 USB" - }, - "description": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03b5\u03bd\u03cc\u03c2 dongle USB Crownstone.", - "title": "\u03a7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 USB dongle Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Crownstone Sphere \u03cc\u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf USB.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 Crownstone Sphere \u03cc\u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf USB.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/en.json b/homeassistant/components/crownstone/translations/en.json index d6070c90a0f..09a26b9739c 100644 --- a/homeassistant/components/crownstone/translations/en.json +++ b/homeassistant/components/crownstone/translations/en.json @@ -56,13 +56,6 @@ "description": "Select the serial port of the Crownstone USB dongle.\n\nLook for a device with VID 10C4 and PID EA60.", "title": "Crownstone USB dongle configuration" }, - "usb_config_option": { - "data": { - "usb_path": "USB Device Path" - }, - "description": "Select the serial port of the Crownstone USB dongle.\n\nLook for a device with VID 10C4 and PID EA60.", - "title": "Crownstone USB dongle configuration" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB Device Path" @@ -70,26 +63,12 @@ "description": "Manually enter the path of a Crownstone USB dongle.", "title": "Crownstone USB dongle manual path" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB Device Path" - }, - "description": "Manually enter the path of a Crownstone USB dongle.", - "title": "Crownstone USB dongle manual path" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "Select a Crownstone Sphere where the USB is located.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "Select a Crownstone Sphere where the USB is located.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/es-419.json b/homeassistant/components/crownstone/translations/es-419.json index 1c904302541..40b4cebe97b 100644 --- a/homeassistant/components/crownstone/translations/es-419.json +++ b/homeassistant/components/crownstone/translations/es-419.json @@ -39,9 +39,6 @@ "usb_config": { "description": "Seleccione el puerto serie del dongle USB Crownstone. \n\n Busque un dispositivo con VID 10C4 y PID EA60.", "title": "Configuraci\u00f3n del dongle USB Crownstone" - }, - "usb_config_option": { - "description": "Seleccione el puerto serie del dongle USB Crownstone. \n\n Busque un dispositivo con VID 10C4 y PID EA60." } } } diff --git a/homeassistant/components/crownstone/translations/es.json b/homeassistant/components/crownstone/translations/es.json index 9e4ac60832f..f52b0074322 100644 --- a/homeassistant/components/crownstone/translations/es.json +++ b/homeassistant/components/crownstone/translations/es.json @@ -56,13 +56,6 @@ "description": "Seleccione el puerto serie del dispositivo USB Crownstone.\n\nBusque un dispositivo con VID 10C4 y PID EA60.", "title": "Configuraci\u00f3n del dispositivo USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "Ruta del dispositivo USB" - }, - "description": "Seleccione el puerto serie del dispositivo USB Crownstone.\n\nBusque un dispositivo con VID 10C4 y PID EA60.", - "title": "Configuraci\u00f3n del dispositivo USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "Ruta del dispositivo USB" @@ -70,26 +63,12 @@ "description": "Introduzca manualmente la ruta de un dispositivo USB Crownstone.", "title": "Ruta manual del dispositivo USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Ruta del dispositivo USB" - }, - "description": "Introduzca manualmente la ruta de un dispositivo USB Crownstone.", - "title": "Ruta manual del dispositivo USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Esfera Crownstone" }, "description": "Selecciona una Esfera Crownstone donde se encuentra el USB.", "title": "USB de Esfera Crownstone" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Esfera Crownstone" - }, - "description": "Selecciona una Esfera Crownstone donde se encuentra el USB.", - "title": "USB de Esfera Crownstone" } } } diff --git a/homeassistant/components/crownstone/translations/et.json b/homeassistant/components/crownstone/translations/et.json index 3a651257e1a..da7831e8959 100644 --- a/homeassistant/components/crownstone/translations/et.json +++ b/homeassistant/components/crownstone/translations/et.json @@ -56,13 +56,6 @@ "description": "Vali Crownstone'i USB seadme jadaport. \n\n Otsi seadet mille VID on 10C4 ja PID on EA60.", "title": "Crownstone'i USB seadme s\u00e4tted" }, - "usb_config_option": { - "data": { - "usb_path": "USB seadme rada" - }, - "description": "Vali Crownstone'i USB seadme jadaport. \n\n Otsi seadet mille VID on 10C4 ja PID on EA60.", - "title": "Crownstone'i USB seadme s\u00e4tted" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB seadme rada" @@ -70,26 +63,12 @@ "description": "Sisesta k\u00e4sitsi Crownstone'i USBseadme rada.", "title": "Crownstone'i USB seadme rada" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB seadme rada" - }, - "description": "Sisesta k\u00e4sitsi Crownstone'i USBseadme rada.", - "title": "Crownstone'i USB seadme rada" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "Vali Crownstone Sphere kus USB asub.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "Vali Crownstone Sphere kus USB asub.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/fr.json b/homeassistant/components/crownstone/translations/fr.json index 61a2f0da436..59f9aa0f20b 100644 --- a/homeassistant/components/crownstone/translations/fr.json +++ b/homeassistant/components/crownstone/translations/fr.json @@ -56,13 +56,6 @@ "description": "S\u00e9lectionnez le port s\u00e9rie du dongle USB Crownstone. \n\n Recherchez un appareil avec VID 10C4 et PID EA60.", "title": "Configuration du dongle USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" - }, - "description": "S\u00e9lectionnez le port s\u00e9rie du dongle USB Crownstone. \n\n Recherchez un appareil avec VID 10C4 et PID EA60.", - "title": "Configuration du dongle USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "Chemin du p\u00e9riph\u00e9rique USB" @@ -70,26 +63,12 @@ "description": "Entrez manuellement le chemin d'un dongle USB Crownstone.", "title": "Chemin d'acc\u00e8s manuel du dongle USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Chemin du p\u00e9riph\u00e9rique USB" - }, - "description": "Entrez manuellement le chemin d'un dongle USB Crownstone.", - "title": "Chemin d'acc\u00e8s manuel du dongle USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "S\u00e9lectionnez une sph\u00e8re Crownstone o\u00f9 se trouve l\u2019USB.", "title": "Sph\u00e8re USB Crownstone" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "S\u00e9lectionnez une sph\u00e8re Crownstone o\u00f9 se trouve l\u2019USB.", - "title": "Sph\u00e8re USB Crownstone" } } } diff --git a/homeassistant/components/crownstone/translations/he.json b/homeassistant/components/crownstone/translations/he.json index af11b65839b..db4289ffebc 100644 --- a/homeassistant/components/crownstone/translations/he.json +++ b/homeassistant/components/crownstone/translations/he.json @@ -33,20 +33,10 @@ "usb_path": "\u05e0\u05ea\u05d9\u05d1 \u05d4\u05ea\u05e7\u05df USB" } }, - "usb_config_option": { - "data": { - "usb_path": "\u05e0\u05ea\u05d9\u05d1 \u05d4\u05ea\u05e7\u05df USB" - } - }, "usb_manual_config": { "data": { "usb_manual_path": "\u05e0\u05ea\u05d9\u05d1 \u05d4\u05ea\u05e7\u05df USB" } - }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "\u05e0\u05ea\u05d9\u05d1 \u05d4\u05ea\u05e7\u05df USB" - } } } } diff --git a/homeassistant/components/crownstone/translations/hu.json b/homeassistant/components/crownstone/translations/hu.json index 2c2a2e34fe1..f2ffe6f2b07 100644 --- a/homeassistant/components/crownstone/translations/hu.json +++ b/homeassistant/components/crownstone/translations/hu.json @@ -56,13 +56,6 @@ "description": "V\u00e1lassza ki a Crownstone USB kulcs soros portj\u00e1t.\n\nKeressen egy VID 10C4 \u00e9s PID EA60 azonos\u00edt\u00f3val rendelkez\u0151 eszk\u00f6zt.", "title": "Crownstone USB kulcs konfigur\u00e1ci\u00f3" }, - "usb_config_option": { - "data": { - "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" - }, - "description": "V\u00e1lassza ki a Crownstone USB kulcs soros portj\u00e1t.\n\nKeressen egy VID 10C4 \u00e9s PID EA60 azonos\u00edt\u00f3val rendelkez\u0151 eszk\u00f6zt.", - "title": "Crownstone USB kulcs konfigur\u00e1ci\u00f3" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" @@ -70,26 +63,12 @@ "description": "Adja meg manu\u00e1lisan a Crownstone USB kulcs \u00fatvonal\u00e1t.", "title": "A Crownstone USB kulcs manu\u00e1lis el\u00e9r\u00e9si \u00fatja" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" - }, - "description": "Adja meg manu\u00e1lisan a Crownstone USB kulcs \u00fatvonal\u00e1t.", - "title": "A Crownstone USB kulcs manu\u00e1lis el\u00e9r\u00e9si \u00fatja" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "V\u00e1lasszon egy Crownstone Sphere-t, ahol az USB tal\u00e1lhat\u00f3.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "V\u00e1lasszon egy Crownstone Sphere-t, ahol az USB tal\u00e1lhat\u00f3.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/id.json b/homeassistant/components/crownstone/translations/id.json index aef98346fd2..381645f1453 100644 --- a/homeassistant/components/crownstone/translations/id.json +++ b/homeassistant/components/crownstone/translations/id.json @@ -56,13 +56,6 @@ "description": "Pilih port serial dongle USB Crownstone. \n\nCari perangkat dengan VID 10C4 dan PID EA60.", "title": "Konfigurasi dongle USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "Jalur Perangkat USB" - }, - "description": "Pilih port serial dongle USB Crownstone. \n\nCari perangkat dengan VID 10C4 dan PID EA60.", - "title": "Konfigurasi dongle USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "Jalur Perangkat USB" @@ -70,26 +63,12 @@ "description": "Masukkan jalur dongle USB Crownstone secara manual.", "title": "Jalur manual dongle USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Jalur Perangkat USB" - }, - "description": "Masukkan jalur dongle USB Crownstone secara manual.", - "title": "Jalur manual dongle USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "Pilih Crownstone Sphere tempat USB berada.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "Pilih Crownstone Sphere tempat USB berada.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/it.json b/homeassistant/components/crownstone/translations/it.json index 062f77c9349..5600ff09de8 100644 --- a/homeassistant/components/crownstone/translations/it.json +++ b/homeassistant/components/crownstone/translations/it.json @@ -56,13 +56,6 @@ "description": "Seleziona la porta seriale della chiavetta USB Crownstone. \n\nCerca un dispositivo con VID 10C4 e PID EA60.", "title": "Configurazione della chiavetta USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "Percorso del dispositivo USB" - }, - "description": "Seleziona la porta seriale della chiavetta USB Crownstone. \n\nCerca un dispositivo con VID 10C4 e PID EA60.", - "title": "Configurazione della chiavetta USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "Percorso del dispositivo USB" @@ -70,26 +63,12 @@ "description": "Immettere manualmente il percorso di una chiavetta USB Crownstone.", "title": "Percorso manuale della chiavetta USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Percorso del dispositivo USB" - }, - "description": "Immetti manualmente il percorso di una chiavetta USB Crownstone.", - "title": "Percorso manuale della chiavetta USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Sfera Crownstone" }, "description": "Seleziona una sfera di Crownstone dove si trova l'USB.", "title": "Sfera USB Crownstone" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Sfera Crownstone" - }, - "description": "Seleziona una sfera Crownstone in cui si trova l'USB.", - "title": "Sfera USB Crownstone" } } } diff --git a/homeassistant/components/crownstone/translations/ja.json b/homeassistant/components/crownstone/translations/ja.json index 6ab8f858af4..639922c4d94 100644 --- a/homeassistant/components/crownstone/translations/ja.json +++ b/homeassistant/components/crownstone/translations/ja.json @@ -56,13 +56,6 @@ "description": "Crownstone USB\u30c9\u30f3\u30b0\u30eb\u306e\u30b7\u30ea\u30a2\u30eb \u30dd\u30fc\u30c8\u3092\u9078\u629e\u3057\u307e\u3059\u3002\n\nVID 10C4 \u3067 PID EA60 \u306a\u5024\u306e\u30c7\u30d0\u30a4\u30b9\u3092\u63a2\u3057\u307e\u3059\u3002", "title": "Crownstone USB\u30c9\u30f3\u30b0\u30eb\u306e\u8a2d\u5b9a" }, - "usb_config_option": { - "data": { - "usb_path": "USB\u30c7\u30d0\u30a4\u30b9\u306e\u30d1\u30b9" - }, - "description": "Crownstone USB\u30c9\u30f3\u30b0\u30eb\u306e\u30b7\u30ea\u30a2\u30eb \u30dd\u30fc\u30c8\u3092\u9078\u629e\u3057\u307e\u3059\u3002\n\nVID 10C4 \u3067 PID EA60 \u306a\u5024\u306e\u30c7\u30d0\u30a4\u30b9\u3092\u63a2\u3057\u307e\u3059\u3002", - "title": "Crownstone USB\u30c9\u30f3\u30b0\u30eb\u306e\u8a2d\u5b9a" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB\u30c7\u30d0\u30a4\u30b9\u306e\u30d1\u30b9" @@ -70,26 +63,12 @@ "description": "\u624b\u52d5\u3067\u3001Crownstone USB\u30c9\u30f3\u30b0\u30eb\u306e\u30d1\u30b9\u3092\u5165\u529b\u3057\u307e\u3059\u3002", "title": "Crownstone USB\u30c9\u30f3\u30b0\u30eb\u3078\u306e\u624b\u52d5\u30d1\u30b9" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB\u30c7\u30d0\u30a4\u30b9\u306e\u30d1\u30b9" - }, - "description": "\u624b\u52d5\u3067\u3001Crownstone USB\u30c9\u30f3\u30b0\u30eb\u306e\u30d1\u30b9\u3092\u5165\u529b\u3057\u307e\u3059\u3002", - "title": "Crownstone USB\u30c9\u30f3\u30b0\u30eb\u3078\u306e\u624b\u52d5\u30d1\u30b9" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "USB\u306b\u914d\u7f6e\u3055\u308c\u3066\u3044\u308b\u3001CrownstoneSphere\u3092\u9078\u629e\u3057\u307e\u3059\u3002", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "USB\u306b\u914d\u7f6e\u3055\u308c\u3066\u3044\u308b\u3001CrownstoneSphere\u3092\u9078\u629e\u3057\u307e\u3059\u3002", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/ko.json b/homeassistant/components/crownstone/translations/ko.json deleted file mode 100644 index aadd2d3da42..00000000000 --- a/homeassistant/components/crownstone/translations/ko.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "options": { - "step": { - "usb_config_option": { - "data": { - "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" - } - }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB \uc7a5\uce58 \uacbd\ub85c" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/crownstone/translations/nl.json b/homeassistant/components/crownstone/translations/nl.json index 1da12c8f841..91dddf8619e 100644 --- a/homeassistant/components/crownstone/translations/nl.json +++ b/homeassistant/components/crownstone/translations/nl.json @@ -56,13 +56,6 @@ "description": "Selecteer de seri\u00eble poort van de Crownstone USB dongle.\n\nZoek naar een apparaat met VID 10C4 en PID EA60.", "title": "Crownstone USB dongle configuratie" }, - "usb_config_option": { - "data": { - "usb_path": "USB-apparaatpad" - }, - "description": "Selecteer de seri\u00eble poort van de Crownstone USB dongle.\n\nZoek naar een apparaat met VID 10C4 en PID EA60.", - "title": "Crownstone USB dongle configuratie" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB-apparaatpad" @@ -70,26 +63,12 @@ "description": "Voer handmatig het pad van een Crownstone USB dongle in.", "title": "Crownstone USB-dongle handmatig pad" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB-apparaatpad" - }, - "description": "Voer handmatig het pad van een Crownstone USB dongle in.", - "title": "Crownstone USB-dongle handmatig pad" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "Selecteer een Crownstone Sphere waar de USB zich bevindt.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "Selecteer een Crownstone Sphere waar de USB zich bevindt.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/no.json b/homeassistant/components/crownstone/translations/no.json index 88f3578a9a4..33e61211205 100644 --- a/homeassistant/components/crownstone/translations/no.json +++ b/homeassistant/components/crownstone/translations/no.json @@ -56,13 +56,6 @@ "description": "Velg serieporten til Crownstone USB -dongelen. \n\n Se etter en enhet med VID 10C4 og PID EA60.", "title": "Crownstone USB -dongle -konfigurasjon" }, - "usb_config_option": { - "data": { - "usb_path": "USB enhetsbane" - }, - "description": "Velg serieporten til Crownstone USB -dongelen. \n\n Se etter en enhet med VID 10C4 og PID EA60.", - "title": "Crownstone USB -dongle -konfigurasjon" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB enhetsbane" @@ -70,26 +63,12 @@ "description": "Skriv inn banen til en Crownstone USB -dongle manuelt.", "title": "Crownstone USB -dongle manuell bane" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB enhetsbane" - }, - "description": "Skriv inn banen til en Crownstone USB -dongle manuelt.", - "title": "Crownstone USB -dongle manuell bane" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "Velg en Crownstone Sphere der USB -en er plassert.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone USB Sphere" - }, - "description": "Velg en Crownstone Sphere der USB -en er plassert.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/pl.json b/homeassistant/components/crownstone/translations/pl.json index 12ac55d668c..49a1d888a94 100644 --- a/homeassistant/components/crownstone/translations/pl.json +++ b/homeassistant/components/crownstone/translations/pl.json @@ -56,13 +56,6 @@ "description": "Wybierz port szeregowy urz\u0105dzenia USB Crownstone. \n\nPoszukaj urz\u0105dzenia z VID 10C4 i PID EA60.", "title": "Konfiguracja urz\u0105dzenia USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" - }, - "description": "Wybierz port szeregowy urz\u0105dzenia USB Crownstone. \n\nPoszukaj urz\u0105dzenia z VID 10C4 i PID EA60.", - "title": "Konfiguracja urz\u0105dzenia USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "\u015acie\u017cka urz\u0105dzenia USB" @@ -70,26 +63,12 @@ "description": "Wprowad\u017a r\u0119cznie \u015bcie\u017ck\u0119 urz\u0105dzenia USB Crownstone.", "title": "Wpisz r\u0119cznie \u015bcie\u017ck\u0119 urz\u0105dzenia USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "\u015acie\u017cka urz\u0105dzenia USB" - }, - "description": "Wprowad\u017a r\u0119cznie \u015bcie\u017ck\u0119 urz\u0105dzenia USB Crownstone.", - "title": "Wpisz r\u0119cznie \u015bcie\u017ck\u0119 urz\u0105dzenia USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "Wybierz USB, w kt\u00f3rym znajduje si\u0119 Crownstone Sphere.", "title": "USB Crownstone Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "Wybierz USB w kt\u00f3rym znajduje si\u0119 Crownstone Sphere.", - "title": "USB Crownstone Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/pt-BR.json b/homeassistant/components/crownstone/translations/pt-BR.json index df4e446837e..f68c7a7f483 100644 --- a/homeassistant/components/crownstone/translations/pt-BR.json +++ b/homeassistant/components/crownstone/translations/pt-BR.json @@ -56,13 +56,6 @@ "description": "Selecione a porta serial do dongle USB Crownstone. \n\n Procure um dispositivo com VID 10C4 e PID EA60.", "title": "Configura\u00e7\u00e3o do dongle USB Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "Caminho do Dispositivo USB" - }, - "description": "Selecione a porta serial do dongle USB Crownstone. \n\n Procure um dispositivo com VID 10C4 e PID EA60.", - "title": "Configura\u00e7\u00e3o do dongle USB Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "Caminho do Dispositivo USB" @@ -70,26 +63,12 @@ "description": "Insira manualmente o caminho de um dongle USB Crownstone.", "title": "Caminho manual do dongle USB Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "Caminho do Dispositivo USB" - }, - "description": "Insira manualmente o caminho de um dongle USB Crownstone.", - "title": "Caminho manual do dongle USB Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Esfera da Pedra da Coroa" }, "description": "Selecione um Crownstone Sphere onde o USB est\u00e1 localizado.", "title": "Esfera USB Crownstone" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Esfera de Crownstone" - }, - "description": "Selecione um Crownstone Sphere onde o USB est\u00e1 localizado.", - "title": "Esfera USB Crownstone" } } } diff --git a/homeassistant/components/crownstone/translations/ru.json b/homeassistant/components/crownstone/translations/ru.json index 7dfd88bd63e..d16b16f457e 100644 --- a/homeassistant/components/crownstone/translations/ru.json +++ b/homeassistant/components/crownstone/translations/ru.json @@ -56,13 +56,6 @@ "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Crownstone. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u044b\u0431\u0438\u0440\u0430\u0439\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 VID 10C4 \u0438 PID EA60.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Crownstone" }, - "usb_config_option": { - "data": { - "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Crownstone. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u044b\u0431\u0438\u0440\u0430\u0439\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 VID 10C4 \u0438 PID EA60.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Crownstone" - }, "usb_manual_config": { "data": { "usb_manual_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" @@ -70,26 +63,12 @@ "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Crownstone.", "title": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Crownstone" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" - }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Crownstone.", - "title": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Crownstone" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 Crownstone Sphere, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 Crownstone Sphere, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/crownstone/translations/tr.json b/homeassistant/components/crownstone/translations/tr.json index 1c97cbdf170..d244ed239a3 100644 --- a/homeassistant/components/crownstone/translations/tr.json +++ b/homeassistant/components/crownstone/translations/tr.json @@ -56,13 +56,6 @@ "description": "Crownstone USB donan\u0131m kilidinin seri ba\u011flant\u0131 noktas\u0131n\u0131 se\u00e7in. \n\n VID 10C4 ve PID EA60'a sahip bir cihaz aray\u0131n.", "title": "Crownstone USB dongle yap\u0131land\u0131rmas\u0131" }, - "usb_config_option": { - "data": { - "usb_path": "USB Cihaz Yolu" - }, - "description": "Crownstone USB donan\u0131m kilidinin seri ba\u011flant\u0131 noktas\u0131n\u0131 se\u00e7in. \n\n VID 10C4 ve PID EA60'a sahip bir cihaz aray\u0131n.", - "title": "Crownstone USB dongle yap\u0131land\u0131rmas\u0131" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB Cihaz Yolu" @@ -70,26 +63,12 @@ "description": "Crownstone USB dongle'\u0131n yolunu manuel olarak girin.", "title": "Crownstone USB dongle manuel yolu" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB Cihaz Yolu" - }, - "description": "Crownstone USB dongle'\u0131n yolunu manuel olarak girin.", - "title": "Crownstone USB dongle manuel yolu" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Stick" }, "description": "USB'nin bulundu\u011fu bir Crownstone Stick se\u00e7in.", "title": "Crownstone USB Stick" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Stick" - }, - "description": "USB'nin bulundu\u011fu bir Crownstone Stick se\u00e7in.", - "title": "Crownstone USB Stick" } } } diff --git a/homeassistant/components/crownstone/translations/zh-Hant.json b/homeassistant/components/crownstone/translations/zh-Hant.json index 2c362ba0bcb..32ef596aba3 100644 --- a/homeassistant/components/crownstone/translations/zh-Hant.json +++ b/homeassistant/components/crownstone/translations/zh-Hant.json @@ -56,13 +56,6 @@ "description": "\u9078\u64c7 Crownstone USB \u88dd\u7f6e\u5e8f\u5217\u57e0\u3002\n\n\u8acb\u641c\u5c0b VID 10C4 \u53ca PID EA60 \u88dd\u7f6e\u3002", "title": "Crownstone USB \u88dd\u7f6e\u8a2d\u5b9a" }, - "usb_config_option": { - "data": { - "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" - }, - "description": "\u9078\u64c7 Crownstone USB \u88dd\u7f6e\u5e8f\u5217\u57e0\u3002\n\n\u8acb\u641c\u5c0b VID 10C4 \u53ca PID EA60 \u88dd\u7f6e\u3002", - "title": "Crownstone USB \u88dd\u7f6e\u8a2d\u5b9a" - }, "usb_manual_config": { "data": { "usb_manual_path": "USB \u88dd\u7f6e\u8def\u5f91" @@ -70,26 +63,12 @@ "description": "\u624b\u52d5\u8f38\u5165 Crownstone USB \u88dd\u7f6e\u8def\u5f91\u3002", "title": "Crownstone USB \u88dd\u7f6e\u624b\u52d5\u8def\u5f91" }, - "usb_manual_config_option": { - "data": { - "usb_manual_path": "USB \u88dd\u7f6e\u8def\u5f91" - }, - "description": "\u624b\u52d5\u8f38\u5165 Crownstone USB \u88dd\u7f6e\u8def\u5f91\u3002", - "title": "Crownstone USB \u88dd\u7f6e\u624b\u52d5\u8def\u5f91" - }, "usb_sphere_config": { "data": { "usb_sphere": "Crownstone Sphere" }, "description": "\u9078\u64c7 Crownstone Sphere \u6240\u5728 USB \u8def\u5f91\u3002", "title": "Crownstone USB Sphere" - }, - "usb_sphere_config_option": { - "data": { - "usb_sphere": "Crownstone Sphere" - }, - "description": "\u9078\u64c7 Crownstone Sphere \u6240\u5728 USB \u8def\u5f91\u3002", - "title": "Crownstone USB Sphere" } } } diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index fb708916039..1b84b182ac8 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -11,24 +11,13 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_PRESET_MODE, ATTR_SWING_MODE, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_ECO, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS @@ -53,27 +42,27 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) HA_STATE_TO_DAIKIN = { - HVAC_MODE_FAN_ONLY: "fan", - HVAC_MODE_DRY: "dry", - HVAC_MODE_COOL: "cool", - HVAC_MODE_HEAT: "hot", - HVAC_MODE_HEAT_COOL: "auto", - HVAC_MODE_OFF: "off", + HVACMode.FAN_ONLY: "fan", + HVACMode.DRY: "dry", + HVACMode.COOL: "cool", + HVACMode.HEAT: "hot", + HVACMode.HEAT_COOL: "auto", + HVACMode.OFF: "off", } DAIKIN_TO_HA_STATE = { - "fan": HVAC_MODE_FAN_ONLY, - "dry": HVAC_MODE_DRY, - "cool": HVAC_MODE_COOL, - "hot": HVAC_MODE_HEAT, - "auto": HVAC_MODE_HEAT_COOL, - "off": HVAC_MODE_OFF, + "fan": HVACMode.FAN_ONLY, + "dry": HVACMode.DRY, + "cool": HVACMode.COOL, + "hot": HVACMode.HEAT, + "auto": HVACMode.HEAT_COOL, + "off": HVACMode.OFF, } HA_STATE_TO_CURRENT_HVAC = { - HVAC_MODE_COOL: CURRENT_HVAC_COOL, - HVAC_MODE_HEAT: CURRENT_HVAC_HEAT, - HVAC_MODE_OFF: CURRENT_HVAC_OFF, + HVACMode.COOL: HVACAction.COOLING, + HVACMode.HEAT: HVACAction.HEATING, + HVACMode.OFF: HVACAction.OFF, } HA_PRESET_TO_DAIKIN = { @@ -135,19 +124,19 @@ class DaikinClimate(ClimateEntity): ATTR_SWING_MODE: self._api.device.swing_modes, } - self._supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE if ( self._api.device.support_away_mode or self._api.device.support_advanced_modes ): - self._supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE if self._api.device.support_fan_rate: - self._supported_features |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE if self._api.device.support_swing_mode: - self._supported_features |= SUPPORT_SWING_MODE + self._attr_supported_features |= ClimateEntityFeature.SWING_MODE async def _set(self, settings): """Set device settings using API.""" @@ -177,11 +166,6 @@ class DaikinClimate(ClimateEntity): if values: await self._api.device.set(values) - @property - def supported_features(self): - """Return the list of supported features.""" - return self._supported_features - @property def name(self): """Return the name of the thermostat, if any.""" @@ -217,29 +201,29 @@ class DaikinClimate(ClimateEntity): await self._set(kwargs) @property - def hvac_action(self): + def hvac_action(self) -> HVACAction | None: """Return the current state.""" ret = HA_STATE_TO_CURRENT_HVAC.get(self.hvac_mode) if ( - ret in (CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT) + ret in (HVACAction.COOLING, HVACAction.HEATING) and self._api.device.support_compressor_frequency and self._api.device.compressor_frequency == 0 ): - return CURRENT_HVAC_IDLE + return HVACAction.IDLE return ret @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" daikin_mode = self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1] - return DAIKIN_TO_HA_STATE.get(daikin_mode, HVAC_MODE_HEAT_COOL) + return DAIKIN_TO_HA_STATE.get(daikin_mode, HVACMode.HEAT_COOL) @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return self._list.get(ATTR_HVAC_MODE) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set HVAC mode.""" await self._set({ATTR_HVAC_MODE: hvac_mode}) @@ -336,7 +320,7 @@ class DaikinClimate(ClimateEntity): async def async_turn_off(self): """Turn device off.""" await self._api.device.set( - {HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVAC_MODE_OFF]} + {HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]} ) @property diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index 39a6f4aa853..1843bdac25f 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -87,6 +87,7 @@ SENSOR_TYPES: tuple[DaikinSensorEntityDescription, ...] = ( key=ATTR_TOTAL_POWER, name="Estimated Power Consumption", device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_KILO_WATT, value_func=lambda device: round(device.current_total_power_consumption, 2), ), @@ -110,6 +111,8 @@ SENSOR_TYPES: tuple[DaikinSensorEntityDescription, ...] = ( key=ATTR_COMPRESSOR_FREQUENCY, name="Compressor Frequency", icon="mdi:fan", + device_class=SensorDeviceClass.FREQUENCY, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=FREQUENCY_HERTZ, value_func=lambda device: device.compressor_frequency, ), diff --git a/homeassistant/components/debugpy/manifest.json b/homeassistant/components/debugpy/manifest.json index 041c2f9e31e..00e4839be63 100644 --- a/homeassistant/components/debugpy/manifest.json +++ b/homeassistant/components/debugpy/manifest.json @@ -2,7 +2,7 @@ "domain": "debugpy", "name": "Remote Python Debugger", "documentation": "https://www.home-assistant.io/integrations/debugpy", - "requirements": ["debugpy==1.5.1"], + "requirements": ["debugpy==1.6.0"], "codeowners": ["@frenck"], "quality_scale": "internal", "iot_class": "local_push" diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index e16e4bcc327..a29c439123e 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -1,10 +1,9 @@ """Support for deCONZ alarm control panel devices.""" from __future__ import annotations -from collections.abc import ValuesView - -from pydeconz.alarm_system import AlarmSystem -from pydeconz.sensor import ( +from pydeconz.models.alarm_system import AlarmSystem +from pydeconz.models.event import EventType +from pydeconz.models.sensor.ancillary_control import ( ANCILLARY_CONTROL_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_NIGHT, ANCILLARY_CONTROL_ARMED_STAY, @@ -20,11 +19,9 @@ from pydeconz.sensor import ( from homeassistant.components.alarm_control_panel import ( DOMAIN, - FORMAT_NUMBER, - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -37,7 +34,6 @@ from homeassistant.const import ( STATE_ALARM_TRIGGERED, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice @@ -72,48 +68,26 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the deCONZ alarm control panel devices. - - Alarm control panels are based on the same device class as sensors in deCONZ. - """ + """Set up the deCONZ alarm control panel devices.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @callback - def async_add_alarm_control_panel( - sensors: list[AncillaryControl] - | ValuesView[AncillaryControl] = gateway.api.sensors.values(), - ) -> None: + def async_add_sensor(_: EventType, sensor_id: str) -> None: """Add alarm control panel devices from deCONZ.""" - entities = [] - - for sensor in sensors: - - if ( - isinstance(sensor, AncillaryControl) - and sensor.unique_id not in gateway.entities[DOMAIN] - and ( - alarm_system := get_alarm_system_for_unique_id( - gateway, sensor.unique_id - ) - ) - is not None - ): - - entities.append(DeconzAlarmControlPanel(sensor, gateway, alarm_system)) - - if entities: - async_add_entities(entities) + sensor = gateway.api.sensors.ancillary_control[sensor_id] + if alarm_system := get_alarm_system_for_unique_id(gateway, sensor.unique_id): + async_add_entities([DeconzAlarmControlPanel(sensor, gateway, alarm_system)]) config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_sensor, - async_add_alarm_control_panel, + gateway.api.sensors.ancillary_control.subscribe( + async_add_sensor, + EventType.ADDED, ) ) - async_add_alarm_control_panel() + for sensor_id in gateway.api.sensors.ancillary_control: + async_add_sensor(EventType.ADDED, sensor_id) class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): @@ -122,9 +96,11 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): TYPE = DOMAIN _device: AncillaryControl - _attr_code_format = FORMAT_NUMBER + _attr_code_format = CodeFormat.NUMBER _attr_supported_features = ( - SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT + AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_NIGHT ) def __init__( @@ -143,27 +119,33 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): keys = {"panel", "reachable"} if ( self._device.changed_keys.intersection(keys) - and self._device.state in DECONZ_TO_ALARM_STATE + and self._device.panel in DECONZ_TO_ALARM_STATE ): super().async_update_callback() @property def state(self) -> str | None: """Return the state of the control panel.""" - return DECONZ_TO_ALARM_STATE.get(self._device.panel) + if self._device.panel in DECONZ_TO_ALARM_STATE: + return DECONZ_TO_ALARM_STATE[self._device.panel] + return None async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" - await self.alarm_system.arm_away(code) + if code: + await self.alarm_system.arm_away(code) async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" - await self.alarm_system.arm_stay(code) + if code: + await self.alarm_system.arm_stay(code) async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" - await self.alarm_system.arm_night(code) + if code: + await self.alarm_system.arm_night(code) async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - await self.alarm_system.disarm(code) + if code: + await self.alarm_system.disarm(code) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 1a37236fae4..c3b16e509cb 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -1,20 +1,18 @@ """Support for deCONZ binary sensors.""" from __future__ import annotations -from collections.abc import Callable, ValuesView +from collections.abc import Callable from dataclasses import dataclass -from pydeconz.sensor import ( - Alarm, - CarbonMonoxide, - DeconzSensor as PydeconzSensor, - Fire, - GenericFlag, - OpenClose, - Presence, - Vibration, - Water, -) +from pydeconz.interfaces.sensors import SensorResources +from pydeconz.models.sensor.alarm import Alarm +from pydeconz.models.sensor.carbon_monoxide import CarbonMonoxide +from pydeconz.models.sensor.fire import Fire +from pydeconz.models.sensor.generic_flag import GenericFlag +from pydeconz.models.sensor.open_close import OpenClose +from pydeconz.models.sensor.presence import Presence +from pydeconz.models.sensor.vibration import Vibration +from pydeconz.models.sensor.water import Water from homeassistant.components.binary_sensor import ( DOMAIN, @@ -55,7 +53,7 @@ class DeconzBinarySensorDescriptionMixin: suffix: str update_key: str - value_fn: Callable[[PydeconzSensor], bool | None] + value_fn: Callable[[SensorResources], bool | None] @dataclass @@ -70,7 +68,7 @@ ENTITY_DESCRIPTIONS = { Alarm: [ DeconzBinarySensorDescription( key="alarm", - value_fn=lambda device: device.alarm, # type: ignore[no-any-return] + value_fn=lambda device: device.alarm if isinstance(device, Alarm) else None, suffix="", update_key="alarm", device_class=BinarySensorDeviceClass.SAFETY, @@ -79,7 +77,9 @@ ENTITY_DESCRIPTIONS = { CarbonMonoxide: [ DeconzBinarySensorDescription( key="carbon_monoxide", - value_fn=lambda device: device.carbon_monoxide, # type: ignore[no-any-return] + value_fn=lambda device: device.carbon_monoxide + if isinstance(device, CarbonMonoxide) + else None, suffix="", update_key="carbonmonoxide", device_class=BinarySensorDeviceClass.CO, @@ -88,14 +88,16 @@ ENTITY_DESCRIPTIONS = { Fire: [ DeconzBinarySensorDescription( key="fire", - value_fn=lambda device: device.fire, # type: ignore[no-any-return] + value_fn=lambda device: device.fire if isinstance(device, Fire) else None, suffix="", update_key="fire", device_class=BinarySensorDeviceClass.SMOKE, ), DeconzBinarySensorDescription( key="in_test_mode", - value_fn=lambda device: device.in_test_mode, # type: ignore[no-any-return] + value_fn=lambda device: device.in_test_mode + if isinstance(device, Fire) + else None, suffix="Test Mode", update_key="test", device_class=BinarySensorDeviceClass.SMOKE, @@ -105,7 +107,9 @@ ENTITY_DESCRIPTIONS = { GenericFlag: [ DeconzBinarySensorDescription( key="flag", - value_fn=lambda device: device.flag, # type: ignore[no-any-return] + value_fn=lambda device: device.flag + if isinstance(device, GenericFlag) + else None, suffix="", update_key="flag", ) @@ -113,7 +117,9 @@ ENTITY_DESCRIPTIONS = { OpenClose: [ DeconzBinarySensorDescription( key="open", - value_fn=lambda device: device.open, # type: ignore[no-any-return] + value_fn=lambda device: device.open + if isinstance(device, OpenClose) + else None, suffix="", update_key="open", device_class=BinarySensorDeviceClass.OPENING, @@ -122,7 +128,9 @@ ENTITY_DESCRIPTIONS = { Presence: [ DeconzBinarySensorDescription( key="presence", - value_fn=lambda device: device.presence, # type: ignore[no-any-return] + value_fn=lambda device: device.presence + if isinstance(device, Presence) + else None, suffix="", update_key="presence", device_class=BinarySensorDeviceClass.MOTION, @@ -131,7 +139,9 @@ ENTITY_DESCRIPTIONS = { Vibration: [ DeconzBinarySensorDescription( key="vibration", - value_fn=lambda device: device.vibration, # type: ignore[no-any-return] + value_fn=lambda device: device.vibration + if isinstance(device, Vibration) + else None, suffix="", update_key="vibration", device_class=BinarySensorDeviceClass.VIBRATION, @@ -140,7 +150,7 @@ ENTITY_DESCRIPTIONS = { Water: [ DeconzBinarySensorDescription( key="water", - value_fn=lambda device: device.water, # type: ignore[no-any-return] + value_fn=lambda device: device.water if isinstance(device, Water) else None, suffix="", update_key="water", device_class=BinarySensorDeviceClass.MOISTURE, @@ -151,7 +161,7 @@ ENTITY_DESCRIPTIONS = { BINARY_SENSOR_DESCRIPTIONS = [ DeconzBinarySensorDescription( key="tampered", - value_fn=lambda device: device.tampered, # type: ignore[no-any-return] + value_fn=lambda device: device.tampered, suffix="Tampered", update_key="tampered", device_class=BinarySensorDeviceClass.TAMPER, @@ -159,7 +169,7 @@ BINARY_SENSOR_DESCRIPTIONS = [ ), DeconzBinarySensorDescription( key="low_battery", - value_fn=lambda device: device.low_battery, # type: ignore[no-any-return] + value_fn=lambda device: device.low_battery, suffix="Low Battery", update_key="lowbattery", device_class=BinarySensorDeviceClass.BATTERY, @@ -178,13 +188,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_sensor( - sensors: list[PydeconzSensor] - | ValuesView[PydeconzSensor] = gateway.api.sensors.values(), - ) -> None: + def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: """Add binary sensor from deCONZ.""" entities: list[DeconzBinarySensor] = [] + if sensors is None: + sensors = gateway.api.sensors.values() + for sensor in sensors: if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): @@ -225,12 +235,12 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): """Representation of a deCONZ binary sensor.""" TYPE = DOMAIN - _device: PydeconzSensor + _device: SensorResources entity_description: DeconzBinarySensorDescription def __init__( self, - device: PydeconzSensor, + device: SensorResources, gateway: DeconzGateway, description: DeconzBinarySensorDescription, ) -> None: diff --git a/homeassistant/components/deconz/button.py b/homeassistant/components/deconz/button.py index 2ad53d8ad63..ed61750af6f 100644 --- a/homeassistant/components/deconz/button.py +++ b/homeassistant/components/deconz/button.py @@ -2,10 +2,10 @@ from __future__ import annotations -from collections.abc import ValuesView from dataclasses import dataclass -from pydeconz.group import Scene as PydeconzScene +from pydeconz.models.event import EventType +from pydeconz.models.scene import Scene as PydeconzScene from homeassistant.components.button import ( DOMAIN, @@ -14,7 +14,6 @@ from homeassistant.components.button import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -58,34 +57,23 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_scene( - scenes: list[PydeconzScene] - | ValuesView[PydeconzScene] = gateway.api.scenes.values(), - ) -> None: + def async_add_scene(_: EventType, scene_id: str) -> None: """Add scene button from deCONZ.""" - entities = [] - - for scene in scenes: - - known_entities = set(gateway.entities[DOMAIN]) - for description in ENTITY_DESCRIPTIONS.get(PydeconzScene, []): - - new_entity = DeconzButton(scene, gateway, description) - if new_entity.unique_id not in known_entities: - entities.append(new_entity) - - if entities: - async_add_entities(entities) + scene = gateway.api.scenes[scene_id] + async_add_entities( + DeconzButton(scene, gateway, description) + for description in ENTITY_DESCRIPTIONS.get(PydeconzScene, []) + ) config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_scene, + gateway.api.scenes.subscribe( async_add_scene, + EventType.ADDED, ) ) - async_add_scene() + for scene_id in gateway.api.scenes: + async_add_scene(EventType.ADDED, scene_id) class DeconzButton(DeconzSceneMixin, ButtonEntity): diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 85ab4b17a1e..aaedc7bae90 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -1,10 +1,9 @@ """Support for deCONZ climate devices.""" from __future__ import annotations -from collections.abc import ValuesView from typing import Any -from pydeconz.sensor import ( +from pydeconz.models.sensor.thermostat import ( THERMOSTAT_FAN_MODE_AUTO, THERMOSTAT_FAN_MODE_HIGH, THERMOSTAT_FAN_MODE_LOW, @@ -34,16 +33,11 @@ from homeassistant.components.climate.const import ( FAN_MEDIUM, FAN_OFF, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -66,14 +60,13 @@ FAN_MODE_TO_DECONZ = { FAN_ON: THERMOSTAT_FAN_MODE_ON, FAN_OFF: THERMOSTAT_FAN_MODE_OFF, } - DECONZ_TO_FAN_MODE = {value: key for key, value in FAN_MODE_TO_DECONZ.items()} -HVAC_MODE_TO_DECONZ = { - HVAC_MODE_AUTO: THERMOSTAT_MODE_AUTO, - HVAC_MODE_COOL: THERMOSTAT_MODE_COOL, - HVAC_MODE_HEAT: THERMOSTAT_MODE_HEAT, - HVAC_MODE_OFF: THERMOSTAT_MODE_OFF, +HVAC_MODE_TO_DECONZ: dict[HVACMode, str] = { + HVACMode.AUTO: THERMOSTAT_MODE_AUTO, + HVACMode.COOL: THERMOSTAT_MODE_COOL, + HVACMode.HEAT: THERMOSTAT_MODE_HEAT, + HVACMode.OFF: THERMOSTAT_MODE_OFF, } DECONZ_PRESET_AUTO = "auto" @@ -90,7 +83,6 @@ PRESET_MODE_TO_DECONZ = { DECONZ_PRESET_HOLIDAY: THERMOSTAT_PRESET_HOLIDAY, DECONZ_PRESET_MANUAL: THERMOSTAT_PRESET_MANUAL, } - DECONZ_TO_PRESET_MODE = {value: key for key, value in PRESET_MODE_TO_DECONZ.items()} @@ -107,13 +99,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_climate( - sensors: list[Thermostat] - | ValuesView[Thermostat] = gateway.api.sensors.values(), - ) -> None: + def async_add_climate(sensors: list[Thermostat] | None = None) -> None: """Add climate devices from deCONZ.""" entities: list[DeconzThermostat] = [] + if sensors is None: + sensors = list(gateway.api.sensors.thermostat.values()) + for sensor in sensors: if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): @@ -151,39 +143,38 @@ class DeconzThermostat(DeconzDevice, ClimateEntity): """Set up thermostat device.""" super().__init__(device, gateway) - self._hvac_mode_to_deconz = dict(HVAC_MODE_TO_DECONZ) - if not device.mode: - self._hvac_mode_to_deconz = { - HVAC_MODE_HEAT: True, - HVAC_MODE_OFF: False, - } - elif "coolsetpoint" not in device.raw["config"]: - self._hvac_mode_to_deconz.pop(HVAC_MODE_COOL) + self._attr_hvac_modes = [ + HVACMode.HEAT, + HVACMode.OFF, + ] + if device.mode: + self._attr_hvac_modes.append(HVACMode.AUTO) + + if "coolsetpoint" in device.raw["config"]: + self._attr_hvac_modes.append(HVACMode.COOL) + self._deconz_to_hvac_mode = { - value: key for key, value in self._hvac_mode_to_deconz.items() + HVAC_MODE_TO_DECONZ[item]: item for item in self._attr_hvac_modes } - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE if device.fan_mode: - self._attr_supported_features |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE + self._attr_fan_modes = list(FAN_MODE_TO_DECONZ) if device.preset: - self._attr_supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE + self._attr_preset_modes = list(PRESET_MODE_TO_DECONZ) # Fan control @property def fan_mode(self) -> str: """Return fan operation.""" - return DECONZ_TO_FAN_MODE.get( - self._device.fan_mode, FAN_ON if self._device.state_on else FAN_OFF - ) - - @property - def fan_modes(self) -> list[str]: - """Return the list of available fan operation modes.""" - return list(FAN_MODE_TO_DECONZ) + if self._device.fan_mode in DECONZ_TO_FAN_MODE: + return DECONZ_TO_FAN_MODE[self._device.fan_mode] + return DECONZ_TO_FAN_MODE[FAN_ON if self._device.state_on else FAN_OFF] async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" @@ -195,43 +186,33 @@ class DeconzThermostat(DeconzDevice, ClimateEntity): # HVAC control @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ - return self._deconz_to_hvac_mode.get( - self._device.mode, - HVAC_MODE_HEAT if self._device.state_on else HVAC_MODE_OFF, - ) + if self._device.mode in self._deconz_to_hvac_mode: + return self._deconz_to_hvac_mode[self._device.mode] + return HVACMode.HEAT if self._device.state_on else HVACMode.OFF - @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes.""" - return list(self._hvac_mode_to_deconz) - - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode not in self._hvac_mode_to_deconz: + if hvac_mode not in self._attr_hvac_modes: raise ValueError(f"Unsupported HVAC mode {hvac_mode}") - data = {"mode": self._hvac_mode_to_deconz[hvac_mode]} - if len(self._hvac_mode_to_deconz) == 2: # Only allow turn on and off thermostat - data = {"on": self._hvac_mode_to_deconz[hvac_mode]} - - await self._device.set_config(**data) + if len(self._attr_hvac_modes) == 2: # Only allow turn on and off thermostat + await self._device.set_config(on=hvac_mode != HVACMode.OFF) + else: + await self._device.set_config(mode=HVAC_MODE_TO_DECONZ[hvac_mode]) # Preset control @property def preset_mode(self) -> str | None: """Return preset mode.""" - return DECONZ_TO_PRESET_MODE.get(self._device.preset) - - @property - def preset_modes(self) -> list: - """Return the list of available preset modes.""" - return list(PRESET_MODE_TO_DECONZ) + if self._device.preset in DECONZ_TO_PRESET_MODE: + return DECONZ_TO_PRESET_MODE[self._device.preset] + return None async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" @@ -245,16 +226,16 @@ class DeconzThermostat(DeconzDevice, ClimateEntity): @property def current_temperature(self) -> float: """Return the current temperature.""" - return self._device.temperature # type: ignore[no-any-return] + return self._device.scaled_temperature @property def target_temperature(self) -> float | None: """Return the target temperature.""" if self._device.mode == THERMOSTAT_MODE_COOL and self._device.cooling_setpoint: - return self._device.cooling_setpoint # type: ignore[no-any-return] + return self._device.cooling_setpoint if self._device.heating_setpoint: - return self._device.heating_setpoint # type: ignore[no-any-return] + return self._device.heating_setpoint return None diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 109afb6190f..28205a7382d 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -8,9 +8,10 @@ from typing import Any, cast from urllib.parse import urlparse import async_timeout -from pydeconz.errors import RequestError, ResponseError +from pydeconz.errors import LinkButtonNotPressed, RequestError, ResponseError from pydeconz.gateway import DeconzSession from pydeconz.utils import ( + DiscoveredBridge, discovery as deconz_discovery, get_bridge_id as deconz_get_bridge_id, normalize_bridge_id, @@ -30,13 +31,15 @@ from .const import ( CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_NEW_DEVICES, - CONF_BRIDGE_ID, + DEFAULT_ALLOW_CLIP_SENSOR, + DEFAULT_ALLOW_DECONZ_GROUPS, + DEFAULT_ALLOW_NEW_DEVICES, DEFAULT_PORT, DOMAIN, HASSIO_CONFIGURATION_URL, LOGGER, ) -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .gateway import DeconzGateway DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" CONF_SERIAL = "serial" @@ -59,6 +62,11 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): _hassio_discovery: dict[str, Any] + bridges: list[DiscoveredBridge] + host: str + port: int + api_key: str + @staticmethod @callback def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: @@ -68,8 +76,6 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the deCONZ config flow.""" self.bridge_id = "" - self.bridges: list[dict[str, int | str]] = [] - self.deconz_config: dict[str, int | str] = {} async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -86,11 +92,9 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): for bridge in self.bridges: if bridge[CONF_HOST] == user_input[CONF_HOST]: - self.bridge_id = cast(str, bridge[CONF_BRIDGE_ID]) - self.deconz_config = { - CONF_HOST: bridge[CONF_HOST], - CONF_PORT: bridge[CONF_PORT], - } + self.bridge_id = bridge["id"] + self.host = bridge[CONF_HOST] + self.port = bridge[CONF_PORT] return await self.async_step_link() session = aiohttp_client.async_get_clientsession(self.hass) @@ -124,7 +128,8 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Manual configuration.""" if user_input: - self.deconz_config = user_input + self.host = user_input[CONF_HOST] + self.port = user_input[CONF_PORT] return await self.async_step_link() return self.async_show_form( @@ -144,26 +149,25 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): errors: dict[str, str] = {} LOGGER.debug( - "Preparing linking with deCONZ gateway %s", pformat(self.deconz_config) + "Preparing linking with deCONZ gateway %s %d", self.host, self.port ) if user_input is not None: session = aiohttp_client.async_get_clientsession(self.hass) - deconz_session = DeconzSession( - session, - host=self.deconz_config[CONF_HOST], - port=self.deconz_config[CONF_PORT], - ) + deconz_session = DeconzSession(session, self.host, self.port) try: async with async_timeout.timeout(10): api_key = await deconz_session.get_api_key() + except LinkButtonNotPressed: + errors["base"] = "linking_not_possible" + except (ResponseError, RequestError, asyncio.TimeoutError): errors["base"] = "no_key" else: - self.deconz_config[CONF_API_KEY] = api_key + self.api_key = api_key return await self._create_entry() return self.async_show_form(step_id="link", errors=errors) @@ -176,42 +180,41 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): try: async with async_timeout.timeout(10): self.bridge_id = await deconz_get_bridge_id( - session, **self.deconz_config + session, self.host, self.port, self.api_key ) await self.async_set_unique_id(self.bridge_id) self._abort_if_unique_id_configured( updates={ - CONF_HOST: self.deconz_config[CONF_HOST], - CONF_PORT: self.deconz_config[CONF_PORT], - CONF_API_KEY: self.deconz_config[CONF_API_KEY], + CONF_HOST: self.host, + CONF_PORT: self.port, + CONF_API_KEY: self.api_key, } ) except asyncio.TimeoutError: return self.async_abort(reason="no_bridges") - return self.async_create_entry(title=self.bridge_id, data=self.deconz_config) + return self.async_create_entry( + title=self.bridge_id, + data={ + CONF_HOST: self.host, + CONF_PORT: self.port, + CONF_API_KEY: self.api_key, + }, + ) async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Trigger a reauthentication flow.""" self.context["title_placeholders"] = {CONF_HOST: config[CONF_HOST]} - self.deconz_config = { - CONF_HOST: config[CONF_HOST], - CONF_PORT: config[CONF_PORT], - } + self.host = config[CONF_HOST] + self.port = config[CONF_PORT] return await self.async_step_link() async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: """Handle a discovered deCONZ bridge.""" - if ( - discovery_info.upnp.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) - != DECONZ_MANUFACTURERURL - ): - return self.async_abort(reason="not_deconz_bridge") - LOGGER.debug("deCONZ SSDP discovery %s", pformat(discovery_info)) self.bridge_id = normalize_bridge_id(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]) @@ -221,22 +224,23 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): if entry and entry.source == config_entries.SOURCE_HASSIO: return self.async_abort(reason="already_configured") - hostname = cast(str, parsed_url.hostname) - port = cast(int, parsed_url.port) + self.host = cast(str, parsed_url.hostname) + self.port = cast(int, parsed_url.port) self._abort_if_unique_id_configured( - updates={CONF_HOST: hostname, CONF_PORT: port} + updates={ + CONF_HOST: self.host, + CONF_PORT: self.port, + } ) self.context.update( { - "title_placeholders": {"host": hostname}, - "configuration_url": f"http://{hostname}:{port}", + "title_placeholders": {"host": self.host}, + "configuration_url": f"http://{self.host}:{self.port}", } ) - self.deconz_config = {CONF_HOST: hostname, CONF_PORT: port} - return await self.async_step_link() async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: @@ -249,16 +253,19 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): self.bridge_id = normalize_bridge_id(discovery_info.config[CONF_SERIAL]) await self.async_set_unique_id(self.bridge_id) + self.host = discovery_info.config[CONF_HOST] + self.port = discovery_info.config[CONF_PORT] + self.api_key = discovery_info.config[CONF_API_KEY] + self._abort_if_unique_id_configured( updates={ - CONF_HOST: discovery_info.config[CONF_HOST], - CONF_PORT: discovery_info.config[CONF_PORT], - CONF_API_KEY: discovery_info.config[CONF_API_KEY], + CONF_HOST: self.host, + CONF_PORT: self.port, + CONF_API_KEY: self.api_key, } ) self.context["configuration_url"] = HASSIO_CONFIGURATION_URL - self._hassio_discovery = discovery_info.config return await self.async_step_hassio_confirm() @@ -269,12 +276,6 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): """Confirm a Hass.io discovery.""" if user_input is not None: - self.deconz_config = { - CONF_HOST: self._hassio_discovery[CONF_HOST], - CONF_PORT: self._hassio_discovery[CONF_PORT], - CONF_API_KEY: self._hassio_discovery[CONF_API_KEY], - } - return await self._create_entry() return self.async_show_form( @@ -297,7 +298,6 @@ class DeconzOptionsFlowHandler(OptionsFlow): self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Manage the deCONZ options.""" - self.gateway = get_gateway_from_config_entry(self.hass, self.config_entry) return await self.async_step_deconz_devices() async def async_step_deconz_devices( @@ -308,22 +308,20 @@ class DeconzOptionsFlowHandler(OptionsFlow): self.options.update(user_input) return self.async_create_entry(title="", data=self.options) + schema_options = {} + for option, default in ( + (CONF_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR), + (CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS), + (CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES), + ): + schema_options[ + vol.Optional( + option, + default=self.config_entry.options.get(option, default), + ) + ] = bool + return self.async_show_form( step_id="deconz_devices", - data_schema=vol.Schema( - { - vol.Optional( - CONF_ALLOW_CLIP_SENSOR, - default=self.gateway.option_allow_clip_sensor, - ): bool, - vol.Optional( - CONF_ALLOW_DECONZ_GROUPS, - default=self.gateway.option_allow_deconz_groups, - ): bool, - vol.Optional( - CONF_ALLOW_NEW_DEVICES, - default=self.gateway.option_allow_new_devices, - ): bool, - } - ), + data_schema=vol.Schema(schema_options), ) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index ca2e791f9e9..60ae610bd5a 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,6 +1,8 @@ """Constants for the deCONZ component.""" import logging +from pydeconz.models import ResourceType + from homeassistant.const import Platform LOGGER = logging.getLogger(__package__) @@ -45,7 +47,12 @@ ATTR_ON = "on" ATTR_VALVE = "valve" # Switches -POWER_PLUGS = ["On/Off light", "On/Off plug-in unit", "Smart plug"] +POWER_PLUGS = [ + ResourceType.ON_OFF_LIGHT.value, + ResourceType.ON_OFF_OUTPUT.value, + ResourceType.ON_OFF_PLUGIN_UNIT.value, + ResourceType.SMART_PLUG.value, +] CONF_ANGLE = "angle" CONF_GESTURE = "gesture" diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index d3696783933..d5656d31bee 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -1,26 +1,17 @@ """Support for deCONZ covers.""" - from __future__ import annotations -from collections.abc import ValuesView from typing import Any, cast -from pydeconz.light import Cover +from pydeconz.models.light.cover import Cover from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -47,12 +38,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_cover( - lights: list[Cover] | ValuesView[Cover] = gateway.api.lights.values(), - ) -> None: + def async_add_cover(lights: list[Cover] | None = None) -> None: """Add cover from deCONZ.""" entities = [] + if lights is None: + lights = list(gateway.api.lights.covers.values()) + for light in lights: if ( isinstance(light, Cover) @@ -84,23 +76,23 @@ class DeconzCover(DeconzDevice, CoverEntity): """Set up cover device.""" super().__init__(device, gateway) - self._attr_supported_features = SUPPORT_OPEN - self._attr_supported_features |= SUPPORT_CLOSE - self._attr_supported_features |= SUPPORT_STOP - self._attr_supported_features |= SUPPORT_SET_POSITION + self._attr_supported_features = CoverEntityFeature.OPEN + self._attr_supported_features |= CoverEntityFeature.CLOSE + self._attr_supported_features |= CoverEntityFeature.STOP + self._attr_supported_features |= CoverEntityFeature.SET_POSITION if self._device.tilt is not None: - self._attr_supported_features |= SUPPORT_OPEN_TILT - self._attr_supported_features |= SUPPORT_CLOSE_TILT - self._attr_supported_features |= SUPPORT_STOP_TILT - self._attr_supported_features |= SUPPORT_SET_TILT_POSITION + self._attr_supported_features |= CoverEntityFeature.OPEN_TILT + self._attr_supported_features |= CoverEntityFeature.CLOSE_TILT + self._attr_supported_features |= CoverEntityFeature.STOP_TILT + self._attr_supported_features |= CoverEntityFeature.SET_TILT_POSITION self._attr_device_class = DEVICE_CLASS.get(self._device.type) @property def current_cover_position(self) -> int: """Return the current position of the cover.""" - return 100 - self._device.lift # type: ignore[no-any-return] + return 100 - self._device.lift @property def is_closed(self) -> bool: @@ -128,7 +120,7 @@ class DeconzCover(DeconzDevice, CoverEntity): def current_cover_tilt_position(self) -> int | None: """Return the current tilt position of the cover.""" if self._device.tilt is not None: - return 100 - self._device.tilt # type: ignore[no-any-return] + return 100 - self._device.tilt return None async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index f429faf54ad..94051cbf0ac 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -2,9 +2,10 @@ from __future__ import annotations -from pydeconz.group import Group as DeconzGroup, Scene as PydeconzScene -from pydeconz.light import DeconzLight -from pydeconz.sensor import DeconzSensor +from pydeconz.models.group import Group as DeconzGroup +from pydeconz.models.light import LightBase as DeconzLight +from pydeconz.models.scene import Scene as PydeconzScene +from pydeconz.models.sensor import SensorBase as DeconzSensor from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE @@ -20,7 +21,7 @@ class DeconzBase: def __init__( self, - device: DeconzGroup | DeconzLight | DeconzSensor, + device: DeconzGroup | DeconzLight | DeconzSensor | PydeconzScene, gateway: DeconzGateway, ) -> None: """Set up device and add update callback to get data from websocket.""" @@ -30,19 +31,21 @@ class DeconzBase: @property def unique_id(self) -> str: """Return a unique identifier for this device.""" + assert not isinstance(self._device, PydeconzScene) return self._device.unique_id @property def serial(self) -> str | None: """Return a serial number for this device.""" + assert not isinstance(self._device, PydeconzScene) if not self._device.unique_id or self._device.unique_id.count(":") != 7: return None - return self._device.unique_id.split("-", 1)[0] @property def device_info(self) -> DeviceInfo | None: """Return a device description for device registry.""" + assert not isinstance(self._device, PydeconzScene) if self.serial is None: return None @@ -66,7 +69,7 @@ class DeconzDevice(DeconzBase, Entity): def __init__( self, - device: DeconzGroup | DeconzLight | DeconzSensor, + device: DeconzGroup | DeconzLight | DeconzSensor | PydeconzScene, gateway: DeconzGateway, ) -> None: """Set up device and add update callback to get data from websocket.""" @@ -109,6 +112,8 @@ class DeconzDevice(DeconzBase, Entity): @property def available(self) -> bool: """Return True if device is available.""" + if isinstance(self._device, PydeconzScene): + return self.gateway.available return self.gateway.available and self._device.reachable @@ -117,7 +122,11 @@ class DeconzSceneMixin(DeconzDevice): _device: PydeconzScene - def __init__(self, device: PydeconzScene, gateway: DeconzGateway) -> None: + def __init__( + self, + device: PydeconzScene, + gateway: DeconzGateway, + ) -> None: """Set up a scene.""" super().__init__(device, gateway) @@ -132,11 +141,6 @@ class DeconzSceneMixin(DeconzDevice): """Describe a unique identifier for group this scene belongs to.""" return f"{self.gateway.bridgeid}-{self._device.group_deconz_id}" - @property - def available(self) -> bool: - """Return True if scene is available.""" - return self.gateway.available - @property def unique_id(self) -> str: """Return a unique identifier for this scene.""" diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index c0b4f763c37..4f04aa34fe2 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -2,14 +2,17 @@ from __future__ import annotations -from pydeconz.sensor import ( +from typing import Any + +from pydeconz.models.event import EventType +from pydeconz.models.sensor.ancillary_control import ( ANCILLARY_CONTROL_EMERGENCY, ANCILLARY_CONTROL_FIRE, ANCILLARY_CONTROL_INVALID_CODE, ANCILLARY_CONTROL_PANIC, AncillaryControl, - Switch, ) +from pydeconz.models.sensor.switch import Switch from homeassistant.const import ( CONF_DEVICE_ID, @@ -20,7 +23,6 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import slugify from .const import CONF_ANGLE, CONF_GESTURE, LOGGER @@ -42,40 +44,42 @@ async def async_setup_events(gateway: DeconzGateway) -> None: """Set up the deCONZ events.""" @callback - def async_add_sensor( - sensors: AncillaryControl | Switch = gateway.api.sensors.values(), - ) -> None: + def async_add_sensor(_: EventType, sensor_id: str) -> None: """Create DeconzEvent.""" - new_events = [] - known_events = {event.unique_id for event in gateway.events} + new_event: DeconzAlarmEvent | DeconzEvent + sensor = gateway.api.sensors[sensor_id] - for sensor in sensors: + if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + return None - if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): - continue + if isinstance(sensor, Switch): + new_event = DeconzEvent(sensor, gateway) - if sensor.unique_id in known_events: - continue + elif isinstance(sensor, AncillaryControl): + new_event = DeconzAlarmEvent(sensor, gateway) - if isinstance(sensor, Switch): - new_events.append(DeconzEvent(sensor, gateway)) + else: + return None - elif isinstance(sensor, AncillaryControl): - new_events.append(DeconzAlarmEvent(sensor, gateway)) - - for new_event in new_events: - gateway.hass.async_create_task(new_event.async_update_device_registry()) - gateway.events.append(new_event) + gateway.hass.async_create_task(new_event.async_update_device_registry()) + gateway.events.append(new_event) gateway.config_entry.async_on_unload( - async_dispatcher_connect( - gateway.hass, - gateway.signal_new_sensor, + gateway.api.sensors.ancillary_control.subscribe( async_add_sensor, + EventType.ADDED, ) ) - async_add_sensor() + gateway.config_entry.async_on_unload( + gateway.api.sensors.switch.subscribe( + async_add_sensor, + EventType.ADDED, + ) + ) + + for sensor_id in gateway.api.sensors: + async_add_sensor(EventType.ADDED, sensor_id) @callback @@ -87,7 +91,7 @@ def async_unload_events(gateway: DeconzGateway) -> None: gateway.events.clear() -class DeconzEvent(DeconzBase): +class DeconzEventBase(DeconzBase): """When you want signals instead of entities. Stateless sensors such as remotes are expected to generate an event @@ -102,21 +106,44 @@ class DeconzEvent(DeconzBase): """Register callback that will be used for signals.""" super().__init__(device, gateway) - self._device.register_callback(self.async_update_callback) + self._unsubscribe = device.subscribe(self.async_update_callback) + self.device = device self.device_id: str | None = None self.event_id = slugify(self._device.name) LOGGER.debug("deCONZ event created: %s", self.event_id) - @property - def device(self) -> AncillaryControl | Switch: - """Return Event device.""" - return self._device - @callback def async_will_remove_from_hass(self) -> None: """Disconnect event object when removed.""" - self._device.remove_callback(self.async_update_callback) + self._unsubscribe() + + @callback + def async_update_callback(self) -> None: + """Fire the event if reason is that state is updated.""" + raise NotImplementedError + + async def async_update_device_registry(self) -> None: + """Update device registry.""" + if not self.device_info: + return + + device_registry = dr.async_get(self.gateway.hass) + + entry = device_registry.async_get_or_create( + config_entry_id=self.gateway.config_entry.entry_id, **self.device_info + ) + self.device_id = entry.id + + +class DeconzEvent(DeconzEventBase): + """When you want signals instead of entities. + + Stateless sensors such as remotes are expected to generate an event + instead of a sensor entity in hass. + """ + + _device: Switch @callback def async_update_callback(self) -> None: @@ -127,10 +154,10 @@ class DeconzEvent(DeconzBase): ): return - data = { + data: dict[str, Any] = { CONF_ID: self.event_id, CONF_UNIQUE_ID: self.serial, - CONF_EVENT: self._device.state, + CONF_EVENT: self._device.button_event, } if self.device_id: @@ -147,20 +174,8 @@ class DeconzEvent(DeconzBase): self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) - async def async_update_device_registry(self) -> None: - """Update device registry.""" - if not self.device_info: - return - device_registry = dr.async_get(self.gateway.hass) - - entry = device_registry.async_get_or_create( - config_entry_id=self.gateway.config_entry.entry_id, **self.device_info - ) - self.device_id = entry.id - - -class DeconzAlarmEvent(DeconzEvent): +class DeconzAlarmEvent(DeconzEventBase): """Alarm control panel companion event when user interacts with a keypad.""" _device: AncillaryControl diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index c76aaf481bf..c92ad7f46dc 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -403,7 +403,7 @@ AQARA_OPPLE_4_BUTTONS = { AQARA_OPPLE_6_BUTTONS_MODEL = "lumi.remote.b686opcn01" AQARA_OPPLE_6_BUTTONS = { **AQARA_OPPLE_4_BUTTONS, - (CONF_LONG_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 5001}, + (CONF_LONG_PRESS, CONF_LEFT): {CONF_EVENT: 5001}, (CONF_SHORT_RELEASE, CONF_LEFT): {CONF_EVENT: 5002}, (CONF_LONG_RELEASE, CONF_LEFT): {CONF_EVENT: 5003}, (CONF_DOUBLE_PRESS, CONF_LEFT): {CONF_EVENT: 5004}, diff --git a/homeassistant/components/deconz/diagnostics.py b/homeassistant/components/deconz/diagnostics.py index d8fdfeae2bd..11854421512 100644 --- a/homeassistant/components/deconz/diagnostics.py +++ b/homeassistant/components/deconz/diagnostics.py @@ -25,7 +25,9 @@ async def async_get_config_entry_diagnostics( diag["deconz_config"] = async_redact_data( gateway.api.config.raw, REDACT_DECONZ_CONFIG ) - diag["websocket_state"] = gateway.api.websocket.state + diag["websocket_state"] = ( + gateway.api.websocket.state if gateway.api.websocket else "Unknown" + ) diag["deconz_ids"] = gateway.deconz_ids diag["entities"] = gateway.entities diag["events"] = { diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 222bf51470f..8fcc7c14f9d 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,10 +1,9 @@ """Support for deCONZ fans.""" from __future__ import annotations -from collections.abc import ValuesView -from typing import Any +from typing import Any, Literal -from pydeconz.light import ( +from pydeconz.models.light.fan import ( FAN_SPEED_25_PERCENT, FAN_SPEED_50_PERCENT, FAN_SPEED_75_PERCENT, @@ -13,7 +12,7 @@ from pydeconz.light import ( Fan, ) -from homeassistant.components.fan import DOMAIN, SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import DOMAIN, FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -26,7 +25,7 @@ from homeassistant.util.percentage import ( from .deconz_device import DeconzDevice from .gateway import DeconzGateway, get_gateway_from_config_entry -ORDERED_NAMED_FAN_SPEEDS = [ +ORDERED_NAMED_FAN_SPEEDS: list[Literal[0, 1, 2, 3, 4, 5, 6]] = [ FAN_SPEED_25_PERCENT, FAN_SPEED_50_PERCENT, FAN_SPEED_75_PERCENT, @@ -44,12 +43,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_fan( - lights: list[Fan] | ValuesView[Fan] = gateway.api.lights.values(), - ) -> None: + def async_add_fan(lights: list[Fan] | None = None) -> None: """Add fan from deCONZ.""" entities = [] + if lights is None: + lights = list(gateway.api.lights.fans.values()) + for light in lights: if ( @@ -77,8 +77,9 @@ class DeconzFan(DeconzDevice, FanEntity): TYPE = DOMAIN _device: Fan + _default_on_speed: Literal[0, 1, 2, 3, 4, 5, 6] - _attr_supported_features = SUPPORT_SET_SPEED + _attr_supported_features = FanEntityFeature.SET_SPEED def __init__(self, device: Fan, gateway: DeconzGateway) -> None: """Set up fan.""" @@ -91,7 +92,7 @@ class DeconzFan(DeconzDevice, FanEntity): @property def is_on(self) -> bool: """Return true if fan is on.""" - return self._device.speed != FAN_SPEED_OFF # type: ignore[no-any-return] + return self._device.speed != FAN_SPEED_OFF @property def percentage(self) -> int | None: diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 821f3f477ab..f54cd4076d3 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -7,11 +7,12 @@ from types import MappingProxyType from typing import TYPE_CHECKING, Any, cast import async_timeout -from pydeconz import DeconzSession, errors, group, light, sensor -from pydeconz.alarm_system import AlarmSystem as DeconzAlarmSystem -from pydeconz.group import Group as DeconzGroup -from pydeconz.light import DeconzLight -from pydeconz.sensor import DeconzSensor +from pydeconz import DeconzSession, errors +from pydeconz.models import ResourceGroup +from pydeconz.models.alarm_system import AlarmSystem as DeconzAlarmSystem +from pydeconz.models.group import Group as DeconzGroup +from pydeconz.models.light import LightBase as DeconzLight +from pydeconz.models.sensor import SensorBase as DeconzSensor from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT @@ -61,23 +62,24 @@ class DeconzGateway: self.ignore_state_updates = False self.signal_reachable = f"deconz-reachable-{config_entry.entry_id}" + self.signal_reload_groups = f"deconz_reload_group_{config_entry.entry_id}" - self.signal_new_group = f"deconz_new_group_{config_entry.entry_id}" self.signal_new_light = f"deconz_new_light_{config_entry.entry_id}" - self.signal_new_scene = f"deconz_new_scene_{config_entry.entry_id}" self.signal_new_sensor = f"deconz_new_sensor_{config_entry.entry_id}" self.deconz_resource_type_to_signal_new_device = { - group.RESOURCE_TYPE: self.signal_new_group, - light.RESOURCE_TYPE: self.signal_new_light, - group.RESOURCE_TYPE_SCENE: self.signal_new_scene, - sensor.RESOURCE_TYPE: self.signal_new_sensor, + ResourceGroup.LIGHT.value: self.signal_new_light, + ResourceGroup.SENSOR.value: self.signal_new_sensor, } self.deconz_ids: dict[str, str] = {} self.entities: dict[str, set[str]] = {} self.events: list[DeconzAlarmEvent | DeconzEvent] = [] + self._option_allow_deconz_groups = self.config_entry.options.get( + CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS + ) + @property def bridgeid(self) -> str: """Return the unique identifier of the gateway.""" @@ -158,6 +160,9 @@ class DeconzGateway: async def async_update_device_registry(self) -> None: """Update device registry.""" + if self.api.config.mac is None: + return + device_registry = dr.async_get(self.hass) # Host device @@ -206,7 +211,7 @@ class DeconzGateway: deconz_ids = [] if self.option_allow_clip_sensor: - self.async_add_device_callback(sensor.RESOURCE_TYPE) + self.async_add_device_callback(ResourceGroup.SENSOR.value) else: deconz_ids += [ @@ -216,11 +221,13 @@ class DeconzGateway: ] if self.option_allow_deconz_groups: - self.async_add_device_callback(group.RESOURCE_TYPE) - + if not self._option_allow_deconz_groups: + async_dispatcher_send(self.hass, self.signal_reload_groups) else: deconz_ids += [group.deconz_id for group in self.api.groups.values()] + self._option_allow_deconz_groups = self.option_allow_deconz_groups + entity_registry = er.async_get(self.hass) for entity_id, deconz_id in self.deconz_ids.items(): @@ -241,7 +248,7 @@ class DeconzGateway: async def async_reset(self) -> bool: """Reset this gateway to default state.""" - self.api.async_connection_status_callback = None + self.api.connection_status_callback = None self.api.close() await self.hass.config_entries.async_unload_platforms( @@ -282,6 +289,6 @@ async def get_deconz_session( LOGGER.warning("Invalid key for deCONZ at %s", config[CONF_HOST]) raise AuthenticationRequired from err - except (asyncio.TimeoutError, errors.RequestError) as err: + except (asyncio.TimeoutError, errors.RequestError, errors.ResponseError) as err: LOGGER.error("Error connecting to deCONZ gateway at %s", config[CONF_HOST]) raise CannotConnect from err diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index d9aeec37fb2..2b03bb0ddb4 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -1,18 +1,18 @@ """Support for deCONZ lights.""" - from __future__ import annotations -from collections.abc import ValuesView -from typing import Any +from typing import Any, Generic, TypedDict, TypeVar -from pydeconz.group import Group -from pydeconz.light import ( +from pydeconz.models import ResourceType +from pydeconz.models.event import EventType +from pydeconz.models.group import Group +from pydeconz.models.light import ( ALERT_LONG, ALERT_SHORT, EFFECT_COLOR_LOOP, EFFECT_NONE, - Light, ) +from pydeconz.models.light.light import Light from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -22,22 +22,17 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_XY_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - COLOR_MODE_XY, DOMAIN, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -51,6 +46,22 @@ DECONZ_GROUP = "is_deconz_group" EFFECT_TO_DECONZ = {EFFECT_COLORLOOP: EFFECT_COLOR_LOOP, "None": EFFECT_NONE} FLASH_TO_DECONZ = {FLASH_SHORT: ALERT_SHORT, FLASH_LONG: ALERT_LONG} +_L = TypeVar("_L", Group, Light) + + +class SetStateAttributes(TypedDict, total=False): + """Attributes available with set state call.""" + + alert: str + brightness: int + color_temperature: int + effect: str + hue: int + on: bool + saturation: int + transition_time: int + xy: tuple[float, float] + async def async_setup_entry( hass: HomeAssistant, @@ -61,171 +72,190 @@ async def async_setup_entry( gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() + entity_registry = er.async_get(hass) + + # On/Off Output should be switch not light 2022.5 + for light in gateway.api.lights.lights.values(): + if light.type == ResourceType.ON_OFF_OUTPUT.value and ( + entity_id := entity_registry.async_get_entity_id( + DOMAIN, DECONZ_DOMAIN, light.unique_id + ) + ): + entity_registry.async_remove(entity_id) + @callback - def async_add_light( - lights: list[Light] | ValuesView[Light] = gateway.api.lights.values(), - ) -> None: + def async_add_light(_: EventType, light_id: str) -> None: """Add light from deCONZ.""" - entities = [] - - for light in lights: - if ( - isinstance(light, Light) - and light.type not in POWER_PLUGS - and light.unique_id not in gateway.entities[DOMAIN] - ): - entities.append(DeconzLight(light, gateway)) - - if entities: - async_add_entities(entities) - - config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_light, - async_add_light, - ) - ) - - @callback - def async_add_group( - groups: list[Group] | ValuesView[Group] = gateway.api.groups.values(), - ) -> None: - """Add group from deCONZ.""" - if not gateway.option_allow_deconz_groups: + light = gateway.api.lights[light_id] + assert isinstance(light, Light) + if light.type in POWER_PLUGS: return - entities = [] + async_add_entities([DeconzLight(light, gateway)]) - for group in groups: - if not group.lights: - continue + config_entry.async_on_unload( + gateway.api.lights.lights.subscribe( + async_add_light, + EventType.ADDED, + ) + ) + for light_id in gateway.api.lights.lights: + async_add_light(EventType.ADDED, light_id) - known_groups = set(gateway.entities[DOMAIN]) - new_group = DeconzGroup(group, gateway) - if new_group.unique_id not in known_groups: - entities.append(new_group) + config_entry.async_on_unload( + gateway.api.lights.fans.subscribe( + async_add_light, + EventType.ADDED, + ) + ) + for light_id in gateway.api.lights.fans: + async_add_light(EventType.ADDED, light_id) - if entities: - async_add_entities(entities) + @callback + def async_add_group(_: EventType, group_id: str) -> None: + """Add group from deCONZ.""" + if ( + not gateway.option_allow_deconz_groups + or (group := gateway.api.groups[group_id]) + and not group.lights + ): + return + + async_add_entities([DeconzGroup(group, gateway)]) + + config_entry.async_on_unload( + gateway.api.groups.subscribe( + async_add_group, + EventType.ADDED, + ) + ) + + @callback + def async_load_groups() -> None: + """Load deCONZ groups.""" + for group_id in gateway.api.groups: + async_add_group(EventType.ADDED, group_id) config_entry.async_on_unload( async_dispatcher_connect( hass, - gateway.signal_new_group, - async_add_group, + gateway.signal_reload_groups, + async_load_groups, ) ) - async_add_light() - async_add_group() + async_load_groups() -class DeconzBaseLight(DeconzDevice, LightEntity): +class DeconzBaseLight(Generic[_L], DeconzDevice, LightEntity): """Representation of a deCONZ light.""" TYPE = DOMAIN - def __init__(self, device: Group | Light, gateway: DeconzGateway) -> None: + _device: _L + + def __init__(self, device: _L, gateway: DeconzGateway) -> None: """Set up light.""" super().__init__(device, gateway) self._attr_supported_color_modes: set[str] = set() if device.color_temp is not None: - self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) if device.hue is not None and device.saturation is not None: - self._attr_supported_color_modes.add(COLOR_MODE_HS) + self._attr_supported_color_modes.add(ColorMode.HS) if device.xy is not None: - self._attr_supported_color_modes.add(COLOR_MODE_XY) + self._attr_supported_color_modes.add(ColorMode.XY) if not self._attr_supported_color_modes and device.brightness is not None: - self._attr_supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) if not self._attr_supported_color_modes: - self._attr_supported_color_modes.add(COLOR_MODE_ONOFF) + self._attr_supported_color_modes.add(ColorMode.ONOFF) if device.brightness is not None: - self._attr_supported_features |= SUPPORT_FLASH - self._attr_supported_features |= SUPPORT_TRANSITION + self._attr_supported_features |= LightEntityFeature.FLASH + self._attr_supported_features |= LightEntityFeature.TRANSITION if device.effect is not None: - self._attr_supported_features |= SUPPORT_EFFECT + self._attr_supported_features |= LightEntityFeature.EFFECT self._attr_effect_list = [EFFECT_COLORLOOP] @property - def color_mode(self) -> str: + def color_mode(self) -> str | None: """Return the color mode of the light.""" if self._device.color_mode == "ct": - color_mode = COLOR_MODE_COLOR_TEMP + color_mode = ColorMode.COLOR_TEMP elif self._device.color_mode == "hs": - color_mode = COLOR_MODE_HS + color_mode = ColorMode.HS elif self._device.color_mode == "xy": - color_mode = COLOR_MODE_XY + color_mode = ColorMode.XY elif self._device.brightness is not None: - color_mode = COLOR_MODE_BRIGHTNESS + color_mode = ColorMode.BRIGHTNESS else: - color_mode = COLOR_MODE_ONOFF + color_mode = ColorMode.ONOFF return color_mode @property def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" - return self._device.brightness # type: ignore[no-any-return] + return self._device.brightness @property - def color_temp(self) -> int: + def color_temp(self) -> int | None: """Return the CT color value.""" - return self._device.color_temp # type: ignore[no-any-return] + return self._device.color_temp @property - def hs_color(self) -> tuple[float, float]: + def hs_color(self) -> tuple[float, float] | None: """Return the hs color value.""" - return (self._device.hue / 65535 * 360, self._device.saturation / 255 * 100) + if (hue := self._device.hue) and (sat := self._device.saturation): + return (hue / 65535 * 360, sat / 255 * 100) + return None @property def xy_color(self) -> tuple[float, float] | None: """Return the XY color value.""" - return self._device.xy # type: ignore[no-any-return] + return self._device.xy @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if light is on.""" - return self._device.state # type: ignore[no-any-return] + return self._device.state async def async_turn_on(self, **kwargs: Any) -> None: """Turn on light.""" - data: dict[str, bool | float | int | str | tuple[float, float]] = {"on": True} + data: SetStateAttributes = {"on": True} - if (attr_brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None: - data["brightness"] = attr_brightness + if ATTR_BRIGHTNESS in kwargs: + data["brightness"] = kwargs[ATTR_BRIGHTNESS] - if attr_color_temp := kwargs.get(ATTR_COLOR_TEMP): - data["color_temperature"] = attr_color_temp + if ATTR_COLOR_TEMP in kwargs: + data["color_temperature"] = kwargs[ATTR_COLOR_TEMP] - if attr_hs_color := kwargs.get(ATTR_HS_COLOR): - if COLOR_MODE_XY in self._attr_supported_color_modes: - data["xy"] = color_hs_to_xy(*attr_hs_color) + if ATTR_HS_COLOR in kwargs: + if ColorMode.XY in self._attr_supported_color_modes: + data["xy"] = color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) else: - data["hue"] = int(attr_hs_color[0] / 360 * 65535) - data["saturation"] = int(attr_hs_color[1] / 100 * 255) + data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535) + data["saturation"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255) if ATTR_XY_COLOR in kwargs: data["xy"] = kwargs[ATTR_XY_COLOR] - if (attr_transition := kwargs.get(ATTR_TRANSITION)) is not None: - data["transition_time"] = int(attr_transition * 10) + if ATTR_TRANSITION in kwargs: + data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) elif "IKEA" in self._device.manufacturer: data["transition_time"] = 0 - if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None: - data["alert"] = alert + if ATTR_FLASH in kwargs and kwargs[ATTR_FLASH] in FLASH_TO_DECONZ: + data["alert"] = FLASH_TO_DECONZ[kwargs[ATTR_FLASH]] del data["on"] - if (effect := EFFECT_TO_DECONZ.get(kwargs.get(ATTR_EFFECT, ""))) is not None: - data["effect"] = effect + if ATTR_EFFECT in kwargs and kwargs[ATTR_EFFECT] in EFFECT_TO_DECONZ: + data["effect"] = EFFECT_TO_DECONZ[kwargs[ATTR_EFFECT]] await self._device.set_state(**data) @@ -234,14 +264,14 @@ class DeconzBaseLight(DeconzDevice, LightEntity): if not self._device.state: return - data: dict[str, bool | int | str] = {"on": False} + data: SetStateAttributes = {"on": False} - if (attr_transition := kwargs.get(ATTR_TRANSITION)) is not None: + if ATTR_TRANSITION in kwargs: data["brightness"] = 0 - data["transition_time"] = int(attr_transition * 10) + data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) - if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None: - data["alert"] = alert + if ATTR_FLASH in kwargs and kwargs[ATTR_FLASH] in FLASH_TO_DECONZ: + data["alert"] = FLASH_TO_DECONZ[kwargs[ATTR_FLASH]] del data["on"] await self._device.set_state(**data) @@ -252,7 +282,7 @@ class DeconzBaseLight(DeconzDevice, LightEntity): return {DECONZ_GROUP: isinstance(self._device, Group)} -class DeconzLight(DeconzBaseLight): +class DeconzLight(DeconzBaseLight[Light]): """Representation of a deCONZ light.""" _device: Light @@ -268,7 +298,7 @@ class DeconzLight(DeconzBaseLight): return self._device.min_color_temp or super().min_mireds -class DeconzGroup(DeconzBaseLight): +class DeconzGroup(DeconzBaseLight[Group]): """Representation of a deCONZ group.""" _device: Group diff --git a/homeassistant/components/deconz/lock.py b/homeassistant/components/deconz/lock.py index 7bdae3e36ed..51c2d4fc0fc 100644 --- a/homeassistant/components/deconz/lock.py +++ b/homeassistant/components/deconz/lock.py @@ -2,11 +2,10 @@ from __future__ import annotations -from collections.abc import ValuesView from typing import Any -from pydeconz.light import Lock -from pydeconz.sensor import DoorLock +from pydeconz.models.light.lock import Lock +from pydeconz.models.sensor.door_lock import DoorLock from homeassistant.components.lock import DOMAIN, LockEntity from homeassistant.config_entries import ConfigEntry @@ -28,12 +27,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_lock_from_light( - lights: list[Lock] | ValuesView[Lock] = gateway.api.lights.values(), - ) -> None: + def async_add_lock_from_light(lights: list[Lock] | None = None) -> None: """Add lock from deCONZ.""" entities = [] + if lights is None: + lights = list(gateway.api.lights.locks.values()) + for light in lights: if ( @@ -54,12 +54,13 @@ async def async_setup_entry( ) @callback - def async_add_lock_from_sensor( - sensors: list[DoorLock] | ValuesView[DoorLock] = gateway.api.sensors.values(), - ) -> None: + def async_add_lock_from_sensor(sensors: list[DoorLock] | None = None) -> None: """Add lock from deCONZ.""" entities = [] + if sensors is None: + sensors = list(gateway.api.sensors.door_lock.values()) + for sensor in sensors: if ( @@ -92,7 +93,7 @@ class DeconzLock(DeconzDevice, LockEntity): @property def is_locked(self) -> bool: """Return true if lock is on.""" - return self._device.is_locked # type: ignore[no-any-return] + return self._device.is_locked async def async_lock(self, **kwargs: Any) -> None: """Lock the lock.""" diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 8ca9b93dbe8..1ce3477db70 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,10 +3,11 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==87"], + "requirements": ["pydeconz==91"], "ssdp": [ { - "manufacturer": "Royal Philips Electronics" + "manufacturer": "Royal Philips Electronics", + "manufacturerURL": "http://www.dresden-elektronik.de" } ], "codeowners": ["@Kane610"], diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index 532cb92ebdf..12ff768cad2 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -2,10 +2,10 @@ from __future__ import annotations -from collections.abc import Callable, ValuesView +from collections.abc import Callable from dataclasses import dataclass -from pydeconz.sensor import PRESENCE_DELAY, DeconzSensor as PydeconzSensor, Presence +from pydeconz.models.sensor.presence import PRESENCE_DELAY, Presence from homeassistant.components.number import ( DOMAIN, @@ -28,7 +28,7 @@ class DeconzNumberDescriptionMixin: suffix: str update_key: str - value_fn: Callable[[PydeconzSensor], float | None] + value_fn: Callable[[Presence], float | None] @dataclass @@ -40,7 +40,7 @@ ENTITY_DESCRIPTIONS = { Presence: [ DeconzNumberDescription( key="delay", - value_fn=lambda device: device.delay, # type: ignore[no-any-return] + value_fn=lambda device: device.delay, suffix="Delay", update_key=PRESENCE_DELAY, max_value=65535, @@ -62,12 +62,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_sensor( - sensors: list[Presence] | ValuesView[Presence] = gateway.api.sensors.values(), - ) -> None: + def async_add_sensor(sensors: list[Presence] | None = None) -> None: """Add number config sensor from deCONZ.""" entities = [] + if sensors is None: + sensors = list(gateway.api.sensors.presence.values()) + for sensor in sensors: if sensor.type.startswith("CLIP"): @@ -98,7 +99,10 @@ async def async_setup_entry( ) async_add_sensor( - [gateway.api.sensors[key] for key in sorted(gateway.api.sensors, key=int)] + [ + gateway.api.sensors.presence[key] + for key in sorted(gateway.api.sensors.presence, key=int) + ] ) @@ -128,9 +132,9 @@ class DeconzNumber(DeconzDevice, NumberEntity): super().async_update_callback() @property - def value(self) -> float: + def value(self) -> float | None: """Return the value of the sensor property.""" - return self.entity_description.value_fn(self._device) # type: ignore[no-any-return] + return self.entity_description.value_fn(self._device) async def async_set_value(self, value: float) -> None: """Set sensor config.""" diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index c188d7faffa..28448da4f75 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -2,15 +2,13 @@ from __future__ import annotations -from collections.abc import ValuesView from typing import Any -from pydeconz.group import Scene as PydeconzScene +from pydeconz.models.event import EventType from homeassistant.components.scene import DOMAIN, Scene from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzSceneMixin @@ -22,37 +20,25 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up scenes for deCONZ component.""" + """Set up scenes for deCONZ integration.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @callback - def async_add_scene( - scenes: list[PydeconzScene] - | ValuesView[PydeconzScene] = gateway.api.scenes.values(), - ) -> None: + def async_add_scene(_: EventType, scene_id: str) -> None: """Add scene from deCONZ.""" - entities = [] - - for scene in scenes: - - known_entities = set(gateway.entities[DOMAIN]) - new_entity = DeconzScene(scene, gateway) - if new_entity.unique_id not in known_entities: - entities.append(new_entity) - - if entities: - async_add_entities(entities) + scene = gateway.api.scenes[scene_id] + async_add_entities([DeconzScene(scene, gateway)]) config_entry.async_on_unload( - async_dispatcher_connect( - hass, - gateway.signal_new_scene, + gateway.api.scenes.subscribe( async_add_scene, + EventType.ADDED, ) ) - async_add_scene() + for scene_id in gateway.api.scenes: + async_add_scene(EventType.ADDED, scene_id) class DeconzScene(DeconzSceneMixin, Scene): diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 95db9625139..d3a20fad522 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -1,24 +1,22 @@ """Support for deCONZ sensors.""" from __future__ import annotations -from collections.abc import Callable, ValuesView +from collections.abc import Callable from dataclasses import dataclass from datetime import datetime -from pydeconz.sensor import ( - AirQuality, - Consumption, - Daylight, - DeconzSensor as PydeconzSensor, - GenericStatus, - Humidity, - LightLevel, - Power, - Pressure, - Switch, - Temperature, - Time, -) +from pydeconz.interfaces.sensors import SensorResources +from pydeconz.models.sensor.air_quality import AirQuality +from pydeconz.models.sensor.consumption import Consumption +from pydeconz.models.sensor.daylight import Daylight +from pydeconz.models.sensor.generic_status import GenericStatus +from pydeconz.models.sensor.humidity import Humidity +from pydeconz.models.sensor.light_level import LightLevel +from pydeconz.models.sensor.power import Power +from pydeconz.models.sensor.pressure import Pressure +from pydeconz.models.sensor.switch import Switch +from pydeconz.models.sensor.temperature import Temperature +from pydeconz.models.sensor.time import Time from homeassistant.components.sensor import ( DOMAIN, @@ -75,7 +73,7 @@ class DeconzSensorDescriptionMixin: """Required values when describing secondary sensor attributes.""" update_key: str - value_fn: Callable[[PydeconzSensor], float | int | str | None] + value_fn: Callable[[SensorResources], float | int | str | None] @dataclass @@ -92,13 +90,17 @@ ENTITY_DESCRIPTIONS = { AirQuality: [ DeconzSensorDescription( key="air_quality", - value_fn=lambda device: device.air_quality, # type: ignore[no-any-return] + value_fn=lambda device: device.air_quality + if isinstance(device, AirQuality) + else None, update_key="airquality", state_class=SensorStateClass.MEASUREMENT, ), DeconzSensorDescription( key="air_quality_ppb", - value_fn=lambda device: device.air_quality_ppb, # type: ignore[no-any-return] + value_fn=lambda device: device.air_quality_ppb + if isinstance(device, AirQuality) + else None, suffix="PPB", update_key="airqualityppb", device_class=SensorDeviceClass.AQI, @@ -109,7 +111,9 @@ ENTITY_DESCRIPTIONS = { Consumption: [ DeconzSensorDescription( key="consumption", - value_fn=lambda device: device.scaled_consumption, # type: ignore[no-any-return] + value_fn=lambda device: device.scaled_consumption + if isinstance(device, Consumption) and isinstance(device.consumption, int) + else None, update_key="consumption", device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, @@ -119,7 +123,9 @@ ENTITY_DESCRIPTIONS = { Daylight: [ DeconzSensorDescription( key="status", - value_fn=lambda device: device.status, # type: ignore[no-any-return] + value_fn=lambda device: device.status + if isinstance(device, Daylight) + else None, update_key="status", icon="mdi:white-balance-sunny", entity_registry_enabled_default=False, @@ -128,14 +134,18 @@ ENTITY_DESCRIPTIONS = { GenericStatus: [ DeconzSensorDescription( key="status", - value_fn=lambda device: device.status, # type: ignore[no-any-return] + value_fn=lambda device: device.status + if isinstance(device, GenericStatus) + else None, update_key="status", ) ], Humidity: [ DeconzSensorDescription( key="humidity", - value_fn=lambda device: device.scaled_humidity, # type: ignore[no-any-return] + value_fn=lambda device: device.scaled_humidity + if isinstance(device, Humidity) and isinstance(device.humidity, int) + else None, update_key="humidity", device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, @@ -145,7 +155,9 @@ ENTITY_DESCRIPTIONS = { LightLevel: [ DeconzSensorDescription( key="light_level", - value_fn=lambda device: device.scaled_light_level, # type: ignore[no-any-return] + value_fn=lambda device: device.scaled_light_level + if isinstance(device, LightLevel) and isinstance(device.light_level, int) + else None, update_key="lightlevel", device_class=SensorDeviceClass.ILLUMINANCE, native_unit_of_measurement=LIGHT_LUX, @@ -154,7 +166,7 @@ ENTITY_DESCRIPTIONS = { Power: [ DeconzSensorDescription( key="power", - value_fn=lambda device: device.power, # type: ignore[no-any-return] + value_fn=lambda device: device.power if isinstance(device, Power) else None, update_key="power", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -164,7 +176,9 @@ ENTITY_DESCRIPTIONS = { Pressure: [ DeconzSensorDescription( key="pressure", - value_fn=lambda device: device.pressure, # type: ignore[no-any-return] + value_fn=lambda device: device.pressure + if isinstance(device, Pressure) + else None, update_key="pressure", device_class=SensorDeviceClass.PRESSURE, state_class=SensorStateClass.MEASUREMENT, @@ -174,7 +188,9 @@ ENTITY_DESCRIPTIONS = { Temperature: [ DeconzSensorDescription( key="temperature", - value_fn=lambda device: device.temperature, # type: ignore[no-any-return] + value_fn=lambda device: device.scaled_temperature + if isinstance(device, Temperature) and isinstance(device.temperature, int) + else None, update_key="temperature", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -184,7 +200,9 @@ ENTITY_DESCRIPTIONS = { Time: [ DeconzSensorDescription( key="last_set", - value_fn=lambda device: device.last_set, # type: ignore[no-any-return] + value_fn=lambda device: device.last_set + if isinstance(device, Time) + else None, update_key="lastset", device_class=SensorDeviceClass.TIMESTAMP, state_class=SensorStateClass.TOTAL_INCREASING, @@ -192,10 +210,11 @@ ENTITY_DESCRIPTIONS = { ], } + SENSOR_DESCRIPTIONS = [ DeconzSensorDescription( key="battery", - value_fn=lambda device: device.battery, # type: ignore[no-any-return] + value_fn=lambda device: device.battery, suffix="Battery", update_key="battery", device_class=SensorDeviceClass.BATTERY, @@ -205,7 +224,7 @@ SENSOR_DESCRIPTIONS = [ ), DeconzSensorDescription( key="secondary_temperature", - value_fn=lambda device: device.secondary_temperature, # type: ignore[no-any-return] + value_fn=lambda device: device.secondary_temperature, suffix="Temperature", update_key="temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -227,10 +246,7 @@ async def async_setup_entry( battery_handler = DeconzBatteryHandler(gateway) @callback - def async_add_sensor( - sensors: list[PydeconzSensor] - | ValuesView[PydeconzSensor] = gateway.api.sensors.values(), - ) -> None: + def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: """Add sensors from deCONZ. Create DeconzBattery if sensor has a battery attribute. @@ -238,6 +254,9 @@ async def async_setup_entry( """ entities: list[DeconzSensor] = [] + if sensors is None: + sensors = gateway.api.sensors.values() + for sensor in sensors: if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): @@ -284,12 +303,12 @@ class DeconzSensor(DeconzDevice, SensorEntity): """Representation of a deCONZ sensor.""" TYPE = DOMAIN - _device: PydeconzSensor + _device: SensorResources entity_description: DeconzSensorDescription def __init__( self, - device: PydeconzSensor, + device: SensorResources, gateway: DeconzGateway, description: DeconzSensorDescription, ) -> None: @@ -333,9 +352,9 @@ class DeconzSensor(DeconzDevice, SensorEntity): def native_value(self) -> StateType | datetime: """Return the state of the sensor.""" if self.entity_description.device_class is SensorDeviceClass.TIMESTAMP: - return dt_util.parse_datetime( - self.entity_description.value_fn(self._device) # type: ignore[arg-type] - ) + value = self.entity_description.value_fn(self._device) + assert isinstance(value, str) + return dt_util.parse_datetime(value) return self.entity_description.value_fn(self._device) @property @@ -381,7 +400,7 @@ class DeconzSensor(DeconzDevice, SensorEntity): class DeconzSensorStateTracker: """Track sensors without a battery state and signal when battery state exist.""" - def __init__(self, sensor: PydeconzSensor, gateway: DeconzGateway) -> None: + def __init__(self, sensor: SensorResources, gateway: DeconzGateway) -> None: """Set up tracker.""" self.sensor = sensor self.gateway = gateway @@ -391,7 +410,6 @@ class DeconzSensorStateTracker: def close(self) -> None: """Clean up tracker.""" self.sensor.remove_callback(self.async_update_callback) - self.sensor = None @callback def async_update_callback(self) -> None: @@ -413,7 +431,7 @@ class DeconzBatteryHandler: self._trackers: set[DeconzSensorStateTracker] = set() @callback - def create_tracker(self, sensor: PydeconzSensor) -> None: + def create_tracker(self, sensor: SensorResources) -> None: """Create new tracker for battery state.""" for tracker in self._trackers: if sensor == tracker.sensor: @@ -421,7 +439,7 @@ class DeconzBatteryHandler: self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway)) @callback - def remove_tracker(self, sensor: PydeconzSensor) -> None: + def remove_tracker(self, sensor: SensorResources) -> None: """Remove tracker of battery state.""" for tracker in self._trackers: if sensor == tracker.sensor: diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index 4b840532fa6..3d12a293c39 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -167,12 +167,13 @@ async def async_remove_orphaned_entries_service(gateway: DeconzGateway) -> None: ] # Don't remove the Gateway host entry - gateway_host = device_registry.async_get_device( - connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)}, - identifiers=set(), - ) - if gateway_host and gateway_host.id in devices_to_be_removed: - devices_to_be_removed.remove(gateway_host.id) + if gateway.api.config.mac: + gateway_host = device_registry.async_get_device( + connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)}, + identifiers=set(), + ) + if gateway_host and gateway_host.id in devices_to_be_removed: + devices_to_be_removed.remove(gateway_host.id) # Don't remove the Gateway service entry gateway_service = device_registry.async_get_device( diff --git a/homeassistant/components/deconz/siren.py b/homeassistant/components/deconz/siren.py index 7f0f6cc39ed..c279afae696 100644 --- a/homeassistant/components/deconz/siren.py +++ b/homeassistant/components/deconz/siren.py @@ -1,19 +1,15 @@ """Support for deCONZ siren.""" - from __future__ import annotations -from collections.abc import ValuesView from typing import Any -from pydeconz.light import Siren +from pydeconz.models.light.siren import Siren from homeassistant.components.siren import ( ATTR_DURATION, DOMAIN, - SUPPORT_DURATION, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SirenEntity, + SirenEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -21,7 +17,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .gateway import get_gateway_from_config_entry async def async_setup_entry( @@ -34,12 +30,13 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_siren( - lights: list[Siren] | ValuesView[Siren] = gateway.api.lights.values(), - ) -> None: + def async_add_siren(lights: list[Siren] | None = None) -> None: """Add siren from deCONZ.""" entities = [] + if lights is None: + lights = list(gateway.api.lights.sirens.values()) + for light in lights: if ( @@ -66,20 +63,17 @@ class DeconzSiren(DeconzDevice, SirenEntity): """Representation of a deCONZ siren.""" TYPE = DOMAIN + _attr_supported_features = ( + SirenEntityFeature.TURN_ON + | SirenEntityFeature.TURN_OFF + | SirenEntityFeature.DURATION + ) _device: Siren - def __init__(self, device: Siren, gateway: DeconzGateway) -> None: - """Set up siren.""" - super().__init__(device, gateway) - - self._attr_supported_features = ( - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_DURATION - ) - @property def is_on(self) -> bool: """Return true if siren is on.""" - return self._device.is_on # type: ignore[no-any-return] + return self._device.is_on async def async_turn_on(self, **kwargs: Any) -> None: """Turn on siren.""" diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index 0fa929f9e63..55bb86d03f6 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -23,6 +23,7 @@ } }, "error": { + "linking_not_possible": "Couldn't link with the gateway", "no_key": "Couldn't get an API key" }, "abort": { @@ -30,7 +31,6 @@ "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "no_bridges": "No deCONZ bridges discovered", "no_hardware_available": "No radio hardware connected to deCONZ", - "not_deconz_bridge": "Not a deCONZ bridge", "updated_instance": "Updated deCONZ instance with new host address" } }, diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index ab4577a427c..f1ff95183dd 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -2,19 +2,17 @@ from __future__ import annotations -from collections.abc import ValuesView from typing import Any -from pydeconz.light import Light, Siren +from pydeconz.models.light.light import Light from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS +from .const import POWER_PLUGS from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry @@ -31,24 +29,14 @@ async def async_setup_entry( gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() - entity_registry = er.async_get(hass) - - # Siren platform replacing sirens in switch platform added in 2021.10 - for light in gateway.api.lights.values(): - if isinstance(light, Siren) and ( - entity_id := entity_registry.async_get_entity_id( - DOMAIN, DECONZ_DOMAIN, light.unique_id - ) - ): - entity_registry.async_remove(entity_id) - @callback - def async_add_switch( - lights: list[Light] | ValuesView[Light] = gateway.api.lights.values(), - ) -> None: + def async_add_switch(lights: list[Light] | None = None) -> None: """Add switch from deCONZ.""" entities = [] + if lights is None: + lights = list(gateway.api.lights.lights.values()) + for light in lights: if ( @@ -80,7 +68,7 @@ class DeconzPowerPlug(DeconzDevice, SwitchEntity): @property def is_on(self) -> bool: """Return true if switch is on.""" - return self._device.state # type: ignore[no-any-return] + return self._device.on async def async_turn_on(self, **kwargs: Any) -> None: """Turn on switch.""" diff --git a/homeassistant/components/deconz/translations/en.json b/homeassistant/components/deconz/translations/en.json index a8e09fd20d7..16034e12414 100644 --- a/homeassistant/components/deconz/translations/en.json +++ b/homeassistant/components/deconz/translations/en.json @@ -9,6 +9,7 @@ "updated_instance": "Updated deCONZ instance with new host address" }, "error": { + "linking_not_possible": "Couldn't link with the gateway", "no_key": "Couldn't get an API key" }, "flow_title": "{host}", diff --git a/homeassistant/components/deconz/translations/he.json b/homeassistant/components/deconz/translations/he.json index 3e2b350a0d9..97274180aef 100644 --- a/homeassistant/components/deconz/translations/he.json +++ b/homeassistant/components/deconz/translations/he.json @@ -12,7 +12,7 @@ "flow_title": "{host}", "step": { "link": { - "description": "\u05d1\u05d8\u05dc \u05d0\u05ea \u05e0\u05e2\u05d9\u05dc\u05ea \u05d4\u05de\u05e9\u05e8 deCONZ \u05e9\u05dc\u05da \u05db\u05d3\u05d9 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05e2\u05dd Home Assistant.\n\n 1. \u05e2\u05d1\u05d5\u05e8 \u05d0\u05dc \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05de\u05e2\u05e8\u05db\u05ea deCONZ \n .2 \u05dc\u05d7\u05e5 \u05e2\u05dc \"Unlock Gateway\"", + "description": "\u05d1\u05d8\u05dc \u05d0\u05ea \u05e0\u05e2\u05d9\u05dc\u05ea \u05d4\u05de\u05e9\u05e8 deCONZ \u05e9\u05dc\u05da \u05db\u05d3\u05d9 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05e2\u05dd Home Assistant.\n\n 1. \u05d9\u05e9 \u05dc\u05e2\u05d1\u05d5\u05e8 \u05d0\u05dc \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea deCONZ > \u05e9\u05e2\u05e8 > \u05de\u05ea\u05e7\u05d3\u05dd\n 2. \u05dc\u05d7\u05d9\u05e6\u05d4 \u05e2\u05dc \u05db\u05e4\u05ea\u05d5\u05e8 \"\u05d0\u05d9\u05de\u05d5\u05ea \u05d9\u05d9\u05e9\u05d5\u05dd\"", "title": "\u05e7\u05e9\u05e8 \u05e2\u05dd deCONZ" }, "manual_input": { diff --git a/homeassistant/components/deconz/translations/hu.json b/homeassistant/components/deconz/translations/hu.json index 4ffc1662ecb..47bb343247c 100644 --- a/homeassistant/components/deconz/translations/hu.json +++ b/homeassistant/components/deconz/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "no_bridges": "Nem tal\u00e1lhat\u00f3 deCONZ \u00e1tj\u00e1r\u00f3", "no_hardware_available": "Nincs deCONZ-hoz csatlakoztatott r\u00e1di\u00f3hardver", "not_deconz_bridge": "Nem egy deCONZ \u00e1tj\u00e1r\u00f3", diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index e29222c8eee..b43881c39b9 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -14,7 +14,7 @@ from homeassistant import util from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_API_KEY, CONF_DEVICES, CONF_NAME @@ -25,8 +25,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) -SUPPORT_DECORA_LED = SUPPORT_BRIGHTNESS - def _name_validator(config): """Validate the name.""" @@ -98,6 +96,9 @@ def setup_platform( class DecoraLight(LightEntity): """Representation of an Decora light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, device): """Initialize the light.""" @@ -128,11 +129,6 @@ class DecoraLight(LightEntity): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_DECORA_LED - @property def assumed_state(self): """We can read the actual state.""" diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 03cbe31b336..7d3b1e353e2 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -15,9 +15,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant @@ -98,11 +98,23 @@ class DecoraWifiLight(LightEntity): self._switch = switch self._attr_unique_id = switch.serial + @property + def color_mode(self) -> str: + """Return the color mode of the light.""" + if self._switch.canSetLevel: + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[str] | None: + """Flag supported color modes.""" + return {self.color_mode} + @property def supported_features(self): """Return supported features.""" if self._switch.canSetLevel: - return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + return LightEntityFeature.TRANSITION return 0 @property diff --git a/homeassistant/components/deluge/translations/bg.json b/homeassistant/components/deluge/translations/bg.json new file mode 100644 index 00000000000..2a0cd494422 --- /dev/null +++ b/homeassistant/components/deluge/translations/bg.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/ca.json b/homeassistant/components/deluge/translations/ca.json new file mode 100644 index 00000000000..df0a2a31ee4 --- /dev/null +++ b/homeassistant/components/deluge/translations/ca.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El servei ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "port": "Port", + "username": "Nom d'usuari", + "web_port": "Port web (per al servei de visita)" + }, + "description": "Per poder utilitzar aquesta integraci\u00f3, has d'activar l'opci\u00f3 seg\u00fcent a la configuraci\u00f3 de Deluge: Daemon > Permet controls remots" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/cs.json b/homeassistant/components/deluge/translations/cs.json new file mode 100644 index 00000000000..2845835ef00 --- /dev/null +++ b/homeassistant/components/deluge/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/de.json b/homeassistant/components/deluge/translations/de.json new file mode 100644 index 00000000000..9e8d559a523 --- /dev/null +++ b/homeassistant/components/deluge/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "port": "Port", + "username": "Benutzername", + "web_port": "Webport (f\u00fcr Besuchsdienste)" + }, + "description": "Um diese Integration nutzen zu k\u00f6nnen, musst du die folgende Option in den Deluge-Einstellungen aktivieren: Daemon > Fernsteuerungen zulassen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/el.json b/homeassistant/components/deluge/translations/el.json new file mode 100644 index 00000000000..48645a7378a --- /dev/null +++ b/homeassistant/components/deluge/translations/el.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "web_port": "\u0398\u03cd\u03c1\u03b1 \u0399\u03c3\u03c4\u03bf\u03cd (\u03b3\u03b9\u03b1 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b5\u03c0\u03af\u03c3\u03ba\u03b5\u03c8\u03b7\u03c2)" + }, + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03bc\u03c0\u03bf\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03c4\u03b1\u03ba\u03bb\u03c5\u03c3\u03bc\u03bf\u03cd: Daemon > \u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/en.json b/homeassistant/components/deluge/translations/en.json index a3a2f539126..3f4e4b0b445 100644 --- a/homeassistant/components/deluge/translations/en.json +++ b/homeassistant/components/deluge/translations/en.json @@ -1,23 +1,23 @@ { "config": { - "step": { - "user": { - "description": "To be able to use this integration, you have to enable the following option in deluge settings: Daemon > Allow remote controls", - "data": { - "host": "Host", - "username": "Username", - "password": "Password", - "port": "Port", - "web_port": "Web port (for visiting service)" - } - } + "abort": { + "already_configured": "Service is already configured" }, "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication" }, - "abort": { - "already_configured": "Service is already configured" + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "username": "Username", + "web_port": "Web port (for visiting service)" + }, + "description": "To be able to use this integration, you have to enable the following option in deluge settings: Daemon > Allow remote controls" + } } } } \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/et.json b/homeassistant/components/deluge/translations/et.json new file mode 100644 index 00000000000..26d8d23438d --- /dev/null +++ b/homeassistant/components/deluge/translations/et.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Teenus on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "port": "Port", + "username": "Kasutajanimi", + "web_port": "Veebiport (teenuse k\u00fclastamiseks)" + }, + "description": "Selle sidumise kasutamiseks deluge s\u00e4tetes lubama j\u00e4rgmise suvandi: Daemon > Luba kaugjuhtimispuldid" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/fr.json b/homeassistant/components/deluge/translations/fr.json new file mode 100644 index 00000000000..4607d78f78c --- /dev/null +++ b/homeassistant/components/deluge/translations/fr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "port": "Port", + "username": "Nom d'utilisateur", + "web_port": "Port web (pour consulter le service)" + }, + "description": "Afin de pouvoir utiliser cette int\u00e9gration, vous devez activer l'option suivante dans les param\u00e8tres de Deluge\u00a0: D\u00e9mon > Autoriser les connexion \u00e0 distance" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/he.json b/homeassistant/components/deluge/translations/he.json new file mode 100644 index 00000000000..80971c19dfc --- /dev/null +++ b/homeassistant/components/deluge/translations/he.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "port": "\u05e4\u05ea\u05d7\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/hu.json b/homeassistant/components/deluge/translations/hu.json new file mode 100644 index 00000000000..6058a985c55 --- /dev/null +++ b/homeassistant/components/deluge/translations/hu.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "host": "C\u00edm", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "web_port": "Web port (a szolg\u00e1ltat\u00e1s l\u00e1togat\u00e1s\u00e1hoz)" + }, + "description": "Ahhoz, hogy ezt az integr\u00e1ci\u00f3t haszn\u00e1lni tudja, enged\u00e9lyeznie kell a k\u00f6vetkez\u0151 opci\u00f3t a be\u00e1ll\u00edt\u00e1sokban: Daemon > Allow remote controls" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/id.json b/homeassistant/components/deluge/translations/id.json new file mode 100644 index 00000000000..8a2fce22fd5 --- /dev/null +++ b/homeassistant/components/deluge/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna", + "web_port": "Port web (untuk mengunjungi layanan)" + }, + "description": "Untuk dapat menggunakan integrasi ini, Anda harus mengaktifkan opsi berikut dalam pengaturan deluge: Daemon > Izinkan kendali jarak jauh" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/it.json b/homeassistant/components/deluge/translations/it.json new file mode 100644 index 00000000000..d9407f0c29f --- /dev/null +++ b/homeassistant/components/deluge/translations/it.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Porta", + "username": "Nome utente", + "web_port": "Porta web (per il servizio di visita)" + }, + "description": "Per poter utilizzare questa integrazione, devi abilitare la seguente opzione nelle impostazioni di diluvio: Demone > Consenti controlli " + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/ja.json b/homeassistant/components/deluge/translations/ja.json new file mode 100644 index 00000000000..ab796327717 --- /dev/null +++ b/homeassistant/components/deluge/translations/ja.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + }, + "step": { + "user": { + "data": { + "host": "\u30db\u30b9\u30c8", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "port": "\u30dd\u30fc\u30c8", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d", + "web_port": "Web\u30dd\u30fc\u30c8\uff08\u8a2a\u554f\u30b5\u30fc\u30d3\u30b9\u7528\uff09" + }, + "description": "\u3053\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u5229\u7528\u3059\u308b\u305f\u3081\u306b\u306f\u3001deluge\u306e\u8a2d\u5b9a\u3067\u4ee5\u4e0b\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u6709\u52b9\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u30c7\u30fc\u30e2\u30f3 -> \u30ea\u30e2\u30fc\u30c8\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u306e\u8a31\u53ef" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/nl.json b/homeassistant/components/deluge/translations/nl.json new file mode 100644 index 00000000000..6c824130708 --- /dev/null +++ b/homeassistant/components/deluge/translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Service is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam", + "web_port": "Webpoort (voor bezoekdienst)" + }, + "description": "Om deze integratie te kunnen gebruiken, moet u de volgende optie inschakelen in de deluge instellingen: Daemon > Afstandsbediening toestaan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/no.json b/homeassistant/components/deluge/translations/no.json new file mode 100644 index 00000000000..02d22dfb1a5 --- /dev/null +++ b/homeassistant/components/deluge/translations/no.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "port": "Port", + "username": "Brukernavn", + "web_port": "Webport (for bes\u00f8kstjeneste)" + }, + "description": "For \u00e5 kunne bruke denne integrasjonen, m\u00e5 du aktivere f\u00f8lgende alternativ i deluge-innstillingene: Daemon > Tillat fjernkontroller" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/pl.json b/homeassistant/components/deluge/translations/pl.json new file mode 100644 index 00000000000..64269b9ab36 --- /dev/null +++ b/homeassistant/components/deluge/translations/pl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika", + "web_port": "Port WWW (do odwiedzania us\u0142ugi)" + }, + "description": "Aby m\u00f3c korzysta\u0107 z tej integracji, musisz w\u0142\u0105czy\u0107 nast\u0119puj\u0105c\u0105 opcj\u0119 w ustawieniach Deluge: Daemon > Zezw\u00f3l na zdalne sterowanie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/pt-BR.json b/homeassistant/components/deluge/translations/pt-BR.json new file mode 100644 index 00000000000..ba45a44117b --- /dev/null +++ b/homeassistant/components/deluge/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "host": "Nome do host", + "password": "Senha", + "port": "Porta", + "username": "Usu\u00e1rio", + "web_port": "Porta Web (para servi\u00e7o de visita)" + }, + "description": "Para poder usar essa integra\u00e7\u00e3o, voc\u00ea deve habilitar a seguinte op\u00e7\u00e3o nas configura\u00e7\u00f5es de Deluge: Daemon > Permitir controles remotos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/ru.json b/homeassistant/components/deluge/translations/ru.json new file mode 100644 index 00000000000..04ab9df50e5 --- /dev/null +++ b/homeassistant/components/deluge/translations/ru.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "web_port": "\u0412\u0435\u0431-\u043f\u043e\u0440\u0442 (\u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0441\u043b\u0443\u0436\u0431\u044b)" + }, + "description": "\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439, \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0439\u0442\u0435 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 Deluge \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435: Daemon > Allow remote controls." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/tr.json b/homeassistant/components/deluge/translations/tr.json new file mode 100644 index 00000000000..0cf6d9ffe35 --- /dev/null +++ b/homeassistant/components/deluge/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "host": "Sunucu", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131", + "web_port": "Web ba\u011flant\u0131 noktas\u0131 (ziyaret hizmeti i\u00e7in)" + }, + "description": "Bu entegrasyonu kullanabilmek i\u00e7in, deluge ayarlar\u0131nda a\u015fa\u011f\u0131daki se\u00e7ene\u011fi etkinle\u015ftirmeniz gerekir: Daemon > Uzaktan kontrollere izin ver" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deluge/translations/zh-Hant.json b/homeassistant/components/deluge/translations/zh-Hant.json new file mode 100644 index 00000000000..c9ad139b2dc --- /dev/null +++ b/homeassistant/components/deluge/translations/zh-Hant.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "username": "\u4f7f\u7528\u8005\u540d\u7a31", + "web_port": "Web \u901a\u8a0a\u57e0\uff08\u8a2a\u554f\u670d\u52d9\uff09" + }, + "description": "\u6b32\u4f7f\u7528\u6b64\u6574\u5408\uff0c\u5fc5\u9808\u5148\u5728 deluge \u8a2d\u5b9a\u4e2d\u958b\u555f\u4ee5\u4e0b\u9078\u9805\uff1aDaemon > \u5141\u8a31\u9060\u7aef\u5b58\u53d6" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/demo/calendar.py b/homeassistant/components/demo/calendar.py index da0badd87b1..42ec04e42f2 100644 --- a/homeassistant/components/demo/calendar.py +++ b/homeassistant/components/demo/calendar.py @@ -2,8 +2,14 @@ from __future__ import annotations import copy +import datetime -from homeassistant.components.calendar import CalendarEventDevice, get_date +from homeassistant.components.calendar import ( + CalendarEntity, + CalendarEvent, + CalendarEventDevice, + get_date, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -17,37 +23,66 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Demo Calendar platform.""" - calendar_data_future = DemoGoogleCalendarDataFuture() - calendar_data_current = DemoGoogleCalendarDataCurrent() add_entities( [ - DemoGoogleCalendar(hass, calendar_data_future, "Calendar 1"), - DemoGoogleCalendar(hass, calendar_data_current, "Calendar 2"), + DemoCalendar(calendar_data_future(), "Calendar 1"), + DemoCalendar(calendar_data_current(), "Calendar 2"), + LegacyDemoCalendar("Calendar 3"), ] ) -class DemoGoogleCalendarData: +def calendar_data_future() -> CalendarEvent: + """Representation of a Demo Calendar for a future event.""" + one_hour_from_now = dt_util.now() + datetime.timedelta(minutes=30) + return CalendarEvent( + start=one_hour_from_now, + end=one_hour_from_now + datetime.timedelta(minutes=60), + summary="Future Event", + ) + + +def calendar_data_current() -> CalendarEvent: + """Representation of a Demo Calendar for a current event.""" + middle_of_event = dt_util.now() - datetime.timedelta(minutes=30) + return CalendarEvent( + start=middle_of_event, + end=middle_of_event + datetime.timedelta(minutes=60), + summary="Current Event", + ) + + +class DemoCalendar(CalendarEntity): """Representation of a Demo Calendar element.""" - event = None + def __init__(self, event: CalendarEvent, name: str) -> None: + """Initialize demo calendar.""" + self._event = event + self._name = name - async def async_get_events(self, hass, start_date, end_date): - """Get all events in a specific time frame.""" - event = copy.copy(self.event) - event["title"] = event["summary"] - event["start"] = get_date(event["start"]).isoformat() - event["end"] = get_date(event["end"]).isoformat() - return [event] + @property + def event(self) -> CalendarEvent: + """Return the next upcoming event.""" + return self._event + + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + + async def async_get_events(self, hass, start_date, end_date) -> list[CalendarEvent]: + """Return calendar events within a datetime range.""" + return [self._event] -class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): - """Representation of a Demo Calendar for a future event.""" +class LegacyDemoCalendar(CalendarEventDevice): + """Calendar for exercising shim API.""" - def __init__(self): - """Set the event to a future event.""" + def __init__(self, name): + """Initialize demo calendar.""" + self._name = name one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30) - self.event = { + self._event = { "start": {"dateTime": one_hour_from_now.isoformat()}, "end": { "dateTime": ( @@ -57,36 +92,10 @@ class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): "summary": "Future Event", } - -class DemoGoogleCalendarDataCurrent(DemoGoogleCalendarData): - """Representation of a Demo Calendar for a current event.""" - - def __init__(self): - """Set the event data.""" - middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30) - self.event = { - "start": {"dateTime": middle_of_event.isoformat()}, - "end": { - "dateTime": ( - middle_of_event + dt_util.dt.timedelta(minutes=60) - ).isoformat() - }, - "summary": "Current Event", - } - - -class DemoGoogleCalendar(CalendarEventDevice): - """Representation of a Demo Calendar element.""" - - def __init__(self, hass, calendar_data, name): - """Initialize demo calendar.""" - self.data = calendar_data - self._name = name - @property def event(self): """Return the next upcoming event.""" - return self.data.event + return self._event @property def name(self): @@ -94,5 +103,9 @@ class DemoGoogleCalendar(CalendarEventDevice): return self._name async def async_get_events(self, hass, start_date, end_date): - """Return calendar events within a datetime range.""" - return await self.data.async_get_events(hass, start_date, end_date) + """Get all events in a specific time frame.""" + event = copy.copy(self.event) + event["title"] = event["summary"] + event["start"] = get_date(event["start"]).isoformat() + event["end"] = get_date(event["end"]).isoformat() + return [event] diff --git a/homeassistant/components/demo/camera.py b/homeassistant/components/demo/camera.py index c1bf54d4629..25026bce11b 100644 --- a/homeassistant/components/demo/camera.py +++ b/homeassistant/components/demo/camera.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path -from homeassistant.components.camera import SUPPORT_ON_OFF, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -39,7 +39,7 @@ class DemoCamera(Camera): _attr_is_streaming = True _attr_motion_detection_enabled = False - _attr_supported_features = SUPPORT_ON_OFF + _attr_supported_features = CameraEntityFeature.ON_OFF def __init__(self, name, content_type): """Initialize demo camera component.""" diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index eb1c38d90bb..a4d6ca6da07 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -5,21 +5,9 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - HVAC_MODES, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -53,12 +41,12 @@ async def async_setup_platform( target_humidity=None, current_humidity=None, swing_mode=None, - hvac_mode=HVAC_MODE_HEAT, - hvac_action=CURRENT_HVAC_HEAT, + hvac_mode=HVACMode.HEAT, + hvac_action=HVACAction.HEATING, aux=None, target_temp_high=None, target_temp_low=None, - hvac_modes=[HVAC_MODE_HEAT, HVAC_MODE_OFF], + hvac_modes=[HVACMode.HEAT, HVACMode.OFF], ), DemoClimate( unique_id="climate_2", @@ -71,12 +59,12 @@ async def async_setup_platform( target_humidity=67, current_humidity=54, swing_mode="Off", - hvac_mode=HVAC_MODE_COOL, - hvac_action=CURRENT_HVAC_COOL, + hvac_mode=HVACMode.COOL, + hvac_action=HVACAction.COOLING, aux=False, target_temp_high=None, target_temp_low=None, - hvac_modes=[mode for mode in HVAC_MODES if mode != HVAC_MODE_HEAT_COOL], + hvac_modes=[cls.value for cls in HVACMode if cls != HVACMode.HEAT_COOL], ), DemoClimate( unique_id="climate_3", @@ -90,12 +78,12 @@ async def async_setup_platform( target_humidity=None, current_humidity=None, swing_mode="Auto", - hvac_mode=HVAC_MODE_HEAT_COOL, + hvac_mode=HVACMode.HEAT_COOL, hvac_action=None, aux=None, target_temp_high=24, target_temp_low=21, - hvac_modes=[HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT], + hvac_modes=[cls.value for cls in HVACMode if cls != HVACMode.HEAT], ), ] ) @@ -138,19 +126,25 @@ class DemoClimate(ClimateEntity): self._name = name self._support_flags = SUPPORT_FLAGS if target_temperature is not None: - self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE + self._support_flags = ( + self._support_flags | ClimateEntityFeature.TARGET_TEMPERATURE + ) if preset is not None: - self._support_flags = self._support_flags | SUPPORT_PRESET_MODE + self._support_flags = self._support_flags | ClimateEntityFeature.PRESET_MODE if fan_mode is not None: - self._support_flags = self._support_flags | SUPPORT_FAN_MODE + self._support_flags = self._support_flags | ClimateEntityFeature.FAN_MODE if target_humidity is not None: - self._support_flags = self._support_flags | SUPPORT_TARGET_HUMIDITY + self._support_flags = ( + self._support_flags | ClimateEntityFeature.TARGET_HUMIDITY + ) if swing_mode is not None: - self._support_flags = self._support_flags | SUPPORT_SWING_MODE + self._support_flags = self._support_flags | ClimateEntityFeature.SWING_MODE if aux is not None: - self._support_flags = self._support_flags | SUPPORT_AUX_HEAT - if HVAC_MODE_HEAT_COOL in hvac_modes or HVAC_MODE_AUTO in hvac_modes: - self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE_RANGE + self._support_flags = self._support_flags | ClimateEntityFeature.AUX_HEAT + if HVACMode.HEAT_COOL in hvac_modes or HVACMode.AUTO in hvac_modes: + self._support_flags = ( + self._support_flags | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) self._target_temperature = target_temperature self._target_humidity = target_humidity self._unit_of_measurement = unit_of_measurement diff --git a/homeassistant/components/demo/cover.py b/homeassistant/components/demo/cover.py index dab85964639..9a1ea6239ee 100644 --- a/homeassistant/components/demo/cover.py +++ b/homeassistant/components/demo/cover.py @@ -4,14 +4,9 @@ from __future__ import annotations from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP_TILT, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -40,7 +35,7 @@ async def async_setup_platform( "cover_4", "Garage Door", device_class=CoverDeviceClass.GARAGE, - supported_features=(SUPPORT_OPEN | SUPPORT_CLOSE), + supported_features=(CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE), ), DemoCover( hass, @@ -48,10 +43,10 @@ async def async_setup_platform( "Pergola Roof", tilt_position=60, supported_features=( - SUPPORT_OPEN_TILT - | SUPPORT_STOP_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION ), ), ] diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 8fcc6a810ed..66440588f17 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -1,13 +1,7 @@ """Demo fan platform that has a fake fan.""" from __future__ import annotations -from homeassistant.components.fan import ( - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -18,8 +12,10 @@ PRESET_MODE_SMART = "smart" PRESET_MODE_SLEEP = "sleep" PRESET_MODE_ON = "on" -FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION -LIMITED_SUPPORT = SUPPORT_SET_SPEED +FULL_SUPPORT = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.OSCILLATE | FanEntityFeature.DIRECTION +) +LIMITED_SUPPORT = FanEntityFeature.SET_SPEED async def async_setup_platform( @@ -78,7 +74,7 @@ async def async_setup_platform( hass, "fan5", "Preset Only Limited Fan", - SUPPORT_PRESET_MODE, + FanEntityFeature.PRESET_MODE, [ PRESET_MODE_AUTO, PRESET_MODE_SMART, @@ -120,9 +116,9 @@ class BaseDemoFan(FanEntity): self._oscillating: bool | None = None self._direction: str | None = None self._name = name - if supported_features & SUPPORT_OSCILLATE: + if supported_features & FanEntityFeature.OSCILLATE: self._oscillating = False - if supported_features & SUPPORT_DIRECTION: + if supported_features & FanEntityFeature.DIRECTION: self._direction = "forward" @property @@ -258,7 +254,7 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): """Set new preset mode.""" if self.preset_modes is None or preset_mode not in self.preset_modes: raise ValueError( - "{preset_mode} is not a valid preset_mode: {self.preset_modes}" + f"{preset_mode} is not a valid preset_mode: {self.preset_modes}" ) self._preset_mode = preset_mode self._percentage = None diff --git a/homeassistant/components/demo/humidifier.py b/homeassistant/components/demo/humidifier.py index 0b366e90efe..c04c44cd8c5 100644 --- a/homeassistant/components/demo/humidifier.py +++ b/homeassistant/components/demo/humidifier.py @@ -2,7 +2,7 @@ from __future__ import annotations from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity -from homeassistant.components.humidifier.const import SUPPORT_MODES +from homeassistant.components.humidifier.const import HumidifierEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -71,7 +71,7 @@ class DemoHumidifier(HumidifierEntity): self._attr_supported_features = SUPPORT_FLAGS if mode is not None: self._attr_supported_features = ( - self._attr_supported_features | SUPPORT_MODES + self._attr_supported_features | HumidifierEntityFeature.MODES ) self._attr_target_humidity = target_humidity self._attr_mode = mode diff --git a/homeassistant/components/demo/light.py b/homeassistant/components/demo/light.py index 9bb3a30686d..a9fc6cf2044 100644 --- a/homeassistant/components/demo/light.py +++ b/homeassistant/components/demo/light.py @@ -11,13 +11,9 @@ from homeassistant.components.light import ( ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, ATTR_WHITE, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_WHITE, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -33,8 +29,8 @@ LIGHT_EFFECT_LIST = ["rainbow", "none"] LIGHT_TEMPS = [240, 380] -SUPPORT_DEMO = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP} -SUPPORT_DEMO_HS_WHITE = {COLOR_MODE_HS, COLOR_MODE_WHITE} +SUPPORT_DEMO = {ColorMode.HS, ColorMode.COLOR_TEMP} +SUPPORT_DEMO_HS_WHITE = {ColorMode.HS, ColorMode.WHITE} async def async_setup_platform( @@ -74,7 +70,7 @@ async def async_setup_platform( name="Office RGBW Lights", rgbw_color=(255, 0, 0, 255), state=True, - supported_color_modes={COLOR_MODE_RGBW}, + supported_color_modes={ColorMode.RGBW}, unique_id="light_4", ), DemoLight( @@ -82,7 +78,7 @@ async def async_setup_platform( name="Living Room RGBWW Lights", rgbww_color=(255, 0, 0, 255, 0), state=True, - supported_color_modes={COLOR_MODE_RGBWW}, + supported_color_modes={ColorMode.RGBWW}, unique_id="light_5", ), DemoLight( @@ -138,18 +134,18 @@ class DemoLight(LightEntity): self._state = state self._unique_id = unique_id if hs_color: - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS elif rgbw_color: - self._color_mode = COLOR_MODE_RGBW + self._color_mode = ColorMode.RGBW elif rgbww_color: - self._color_mode = COLOR_MODE_RGBWW + self._color_mode = ColorMode.RGBWW else: - self._color_mode = COLOR_MODE_COLOR_TEMP + self._color_mode = ColorMode.COLOR_TEMP if not supported_color_modes: supported_color_modes = SUPPORT_DEMO self._color_modes = supported_color_modes if self._effect_list is not None: - self._features |= SUPPORT_EFFECT + self._features |= LightEntityFeature.EFFECT @property def device_info(self) -> DeviceInfo: @@ -247,26 +243,26 @@ class DemoLight(LightEntity): self._brightness = kwargs[ATTR_BRIGHTNESS] if ATTR_COLOR_TEMP in kwargs: - self._color_mode = COLOR_MODE_COLOR_TEMP + self._color_mode = ColorMode.COLOR_TEMP self._ct = kwargs[ATTR_COLOR_TEMP] if ATTR_EFFECT in kwargs: self._effect = kwargs[ATTR_EFFECT] if ATTR_HS_COLOR in kwargs: - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS self._hs_color = kwargs[ATTR_HS_COLOR] if ATTR_RGBW_COLOR in kwargs: - self._color_mode = COLOR_MODE_RGBW + self._color_mode = ColorMode.RGBW self._rgbw_color = kwargs[ATTR_RGBW_COLOR] if ATTR_RGBWW_COLOR in kwargs: - self._color_mode = COLOR_MODE_RGBWW + self._color_mode = ColorMode.RGBWW self._rgbww_color = kwargs[ATTR_RGBWW_COLOR] if ATTR_WHITE in kwargs: - self._color_mode = COLOR_MODE_WHITE + self._color_mode = ColorMode.WHITE self._brightness = kwargs[ATTR_WHITE] # As we have disabled polling, we need to inform diff --git a/homeassistant/components/demo/lock.py b/homeassistant/components/demo/lock.py index c44d10ed8f1..86188e8b935 100644 --- a/homeassistant/components/demo/lock.py +++ b/homeassistant/components/demo/lock.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio -from homeassistant.components.lock import SUPPORT_OPEN, LockEntity +from homeassistant.components.lock import LockEntity, LockEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_JAMMED, @@ -60,7 +60,7 @@ class DemoLock(LockEntity): """Initialize the lock.""" self._attr_name = name if openable: - self._attr_supported_features = SUPPORT_OPEN + self._attr_supported_features = LockEntityFeature.OPEN self._state = state self._openable = openable self._jam_on_operation = jam_on_operation @@ -113,5 +113,5 @@ class DemoLock(LockEntity): def supported_features(self): """Flag supported features.""" if self._openable: - return SUPPORT_OPEN + return LockEntityFeature.OPEN return 0 diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index 661e218a1ad..f3046a6f807 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -10,24 +10,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, REPEAT_MODE_OFF, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_GROUPING, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING @@ -75,48 +58,48 @@ SOUND_MODE_LIST = ["Music", "Movie"] DEFAULT_SOUND_MODE = "Music" YOUTUBE_PLAYER_SUPPORT = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_SHUFFLE_SET - | SUPPORT_SELECT_SOUND_MODE - | SUPPORT_SEEK - | SUPPORT_STOP + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.SELECT_SOUND_MODE + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.STOP ) MUSIC_PLAYER_SUPPORT = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_GROUPING - | SUPPORT_PLAY - | SUPPORT_SHUFFLE_SET - | SUPPORT_REPEAT_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SELECT_SOUND_MODE - | SUPPORT_STOP + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.GROUPING + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SELECT_SOUND_MODE + | MediaPlayerEntityFeature.STOP ) NETFLIX_PLAYER_SUPPORT = ( - SUPPORT_PAUSE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY - | SUPPORT_SHUFFLE_SET - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SELECT_SOUND_MODE - | SUPPORT_STOP + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SELECT_SOUND_MODE + | MediaPlayerEntityFeature.STOP ) diff --git a/homeassistant/components/demo/siren.py b/homeassistant/components/demo/siren.py index 9cf18a4c902..0720114861c 100644 --- a/homeassistant/components/demo/siren.py +++ b/homeassistant/components/demo/siren.py @@ -3,20 +3,13 @@ from __future__ import annotations from typing import Any -from homeassistant.components.siren import SirenEntity -from homeassistant.components.siren.const import ( - SUPPORT_DURATION, - SUPPORT_TONES, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_SET, -) +from homeassistant.components.siren import SirenEntity, SirenEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -SUPPORT_FLAGS = SUPPORT_TURN_OFF | SUPPORT_TURN_ON +SUPPORT_FLAGS = SirenEntityFeature.TURN_OFF | SirenEntityFeature.TURN_ON async def async_setup_platform( @@ -65,11 +58,11 @@ class DemoSiren(SirenEntity): self._attr_supported_features = SUPPORT_FLAGS self._attr_is_on = is_on if available_tones is not None: - self._attr_supported_features |= SUPPORT_TONES + self._attr_supported_features |= SirenEntityFeature.TONES if support_volume_set: - self._attr_supported_features |= SUPPORT_VOLUME_SET + self._attr_supported_features |= SirenEntityFeature.VOLUME_SET if support_duration: - self._attr_supported_features |= SUPPORT_DURATION + self._attr_supported_features |= SirenEntityFeature.DURATION self._attr_available_tones = available_tones async def async_turn_on(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/demo/translations/it.json b/homeassistant/components/demo/translations/it.json index 0fb3f52b916..1c94dea053f 100644 --- a/homeassistant/components/demo/translations/it.json +++ b/homeassistant/components/demo/translations/it.json @@ -3,8 +3,8 @@ "step": { "init": { "data": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" } }, "options_1": { diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index bb390111285..feda379558b 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -8,21 +8,9 @@ from homeassistant.components.vacuum import ( STATE_IDLE, STATE_PAUSED, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STATUS, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, StateVacuumEntity, VacuumEntity, + VacuumEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -30,44 +18,47 @@ from homeassistant.helpers import event from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -SUPPORT_MINIMAL_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF +SUPPORT_MINIMAL_SERVICES = VacuumEntityFeature.TURN_ON | VacuumEntityFeature.TURN_OFF SUPPORT_BASIC_SERVICES = ( - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_STATUS | SUPPORT_BATTERY + VacuumEntityFeature.TURN_ON + | VacuumEntityFeature.TURN_OFF + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.BATTERY ) SUPPORT_MOST_SERVICES = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_STOP - | SUPPORT_RETURN_HOME - | SUPPORT_STATUS - | SUPPORT_BATTERY + VacuumEntityFeature.TURN_ON + | VacuumEntityFeature.TURN_OFF + | VacuumEntityFeature.STOP + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.BATTERY ) SUPPORT_ALL_SERVICES = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_RETURN_HOME - | SUPPORT_FAN_SPEED - | SUPPORT_SEND_COMMAND - | SUPPORT_LOCATE - | SUPPORT_STATUS - | SUPPORT_BATTERY - | SUPPORT_CLEAN_SPOT + VacuumEntityFeature.TURN_ON + | VacuumEntityFeature.TURN_OFF + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT ) SUPPORT_STATE_SERVICES = ( - SUPPORT_STATE - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_RETURN_HOME - | SUPPORT_FAN_SPEED - | SUPPORT_BATTERY - | SUPPORT_CLEAN_SPOT - | SUPPORT_START + VacuumEntityFeature.STATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.START ) FAN_SPEEDS = ["min", "medium", "high", "max"] @@ -167,7 +158,7 @@ class DemoVacuum(VacuumEntity): def turn_on(self, **kwargs): """Turn the vacuum on.""" - if self.supported_features & SUPPORT_TURN_ON == 0: + if self.supported_features & VacuumEntityFeature.TURN_ON == 0: return self._state = True @@ -178,7 +169,7 @@ class DemoVacuum(VacuumEntity): def turn_off(self, **kwargs): """Turn the vacuum off.""" - if self.supported_features & SUPPORT_TURN_OFF == 0: + if self.supported_features & VacuumEntityFeature.TURN_OFF == 0: return self._state = False @@ -187,7 +178,7 @@ class DemoVacuum(VacuumEntity): def stop(self, **kwargs): """Stop the vacuum.""" - if self.supported_features & SUPPORT_STOP == 0: + if self.supported_features & VacuumEntityFeature.STOP == 0: return self._state = False @@ -196,7 +187,7 @@ class DemoVacuum(VacuumEntity): def clean_spot(self, **kwargs): """Perform a spot clean-up.""" - if self.supported_features & SUPPORT_CLEAN_SPOT == 0: + if self.supported_features & VacuumEntityFeature.CLEAN_SPOT == 0: return self._state = True @@ -207,7 +198,7 @@ class DemoVacuum(VacuumEntity): def locate(self, **kwargs): """Locate the vacuum (usually by playing a song).""" - if self.supported_features & SUPPORT_LOCATE == 0: + if self.supported_features & VacuumEntityFeature.LOCATE == 0: return self._status = "Hi, I'm over here!" @@ -215,7 +206,7 @@ class DemoVacuum(VacuumEntity): def start_pause(self, **kwargs): """Start, pause or resume the cleaning task.""" - if self.supported_features & SUPPORT_PAUSE == 0: + if self.supported_features & VacuumEntityFeature.PAUSE == 0: return self._state = not self._state @@ -229,7 +220,7 @@ class DemoVacuum(VacuumEntity): def set_fan_speed(self, fan_speed, **kwargs): """Set the vacuum's fan speed.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: + if self.supported_features & VacuumEntityFeature.FAN_SPEED == 0: return if fan_speed in self.fan_speed_list: @@ -238,7 +229,7 @@ class DemoVacuum(VacuumEntity): def return_to_base(self, **kwargs): """Tell the vacuum to return to its dock.""" - if self.supported_features & SUPPORT_RETURN_HOME == 0: + if self.supported_features & VacuumEntityFeature.RETURN_HOME == 0: return self._state = False @@ -248,7 +239,7 @@ class DemoVacuum(VacuumEntity): def send_command(self, command, params=None, **kwargs): """Send a command to the vacuum.""" - if self.supported_features & SUPPORT_SEND_COMMAND == 0: + if self.supported_features & VacuumEntityFeature.SEND_COMMAND == 0: return self._status = f"Executing {command}({params})" @@ -310,7 +301,7 @@ class StateDemoVacuum(StateVacuumEntity): def start(self): """Start or resume the cleaning task.""" - if self.supported_features & SUPPORT_START == 0: + if self.supported_features & VacuumEntityFeature.START == 0: return if self._state != STATE_CLEANING: @@ -321,7 +312,7 @@ class StateDemoVacuum(StateVacuumEntity): def pause(self): """Pause the cleaning task.""" - if self.supported_features & SUPPORT_PAUSE == 0: + if self.supported_features & VacuumEntityFeature.PAUSE == 0: return if self._state == STATE_CLEANING: @@ -330,7 +321,7 @@ class StateDemoVacuum(StateVacuumEntity): def stop(self, **kwargs): """Stop the cleaning task, do not return to dock.""" - if self.supported_features & SUPPORT_STOP == 0: + if self.supported_features & VacuumEntityFeature.STOP == 0: return self._state = STATE_IDLE @@ -338,7 +329,7 @@ class StateDemoVacuum(StateVacuumEntity): def return_to_base(self, **kwargs): """Return dock to charging base.""" - if self.supported_features & SUPPORT_RETURN_HOME == 0: + if self.supported_features & VacuumEntityFeature.RETURN_HOME == 0: return self._state = STATE_RETURNING @@ -348,7 +339,7 @@ class StateDemoVacuum(StateVacuumEntity): def clean_spot(self, **kwargs): """Perform a spot clean-up.""" - if self.supported_features & SUPPORT_CLEAN_SPOT == 0: + if self.supported_features & VacuumEntityFeature.CLEAN_SPOT == 0: return self._state = STATE_CLEANING @@ -358,7 +349,7 @@ class StateDemoVacuum(StateVacuumEntity): def set_fan_speed(self, fan_speed, **kwargs): """Set the vacuum's fan speed.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: + if self.supported_features & VacuumEntityFeature.FAN_SPEED == 0: return if fan_speed in self.fan_speed_list: diff --git a/homeassistant/components/demo/water_heater.py b/homeassistant/components/demo/water_heater.py index 7b3ecece88c..70332277d90 100644 --- a/homeassistant/components/demo/water_heater.py +++ b/homeassistant/components/demo/water_heater.py @@ -2,10 +2,8 @@ from __future__ import annotations from homeassistant.components.water_heater import ( - SUPPORT_AWAY_MODE, - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -14,7 +12,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType SUPPORT_FLAGS_HEATER = ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + | WaterHeaterEntityFeature.AWAY_MODE ) @@ -55,13 +55,15 @@ class DemoWaterHeater(WaterHeaterEntity): self._attr_name = name if target_temperature is not None: self._attr_supported_features = ( - self.supported_features | SUPPORT_TARGET_TEMPERATURE + self.supported_features | WaterHeaterEntityFeature.TARGET_TEMPERATURE ) if away is not None: - self._attr_supported_features = self.supported_features | SUPPORT_AWAY_MODE + self._attr_supported_features = ( + self.supported_features | WaterHeaterEntityFeature.AWAY_MODE + ) if current_operation is not None: self._attr_supported_features = ( - self.supported_features | SUPPORT_OPERATION_MODE + self.supported_features | WaterHeaterEntityFeature.OPERATION_MODE ) self._attr_target_temperature = target_temperature self._attr_temperature_unit = unit_of_measurement diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index d326f4b5c6a..d55adcf5db7 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -6,18 +6,10 @@ import telnetlib import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant @@ -30,18 +22,18 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Music station" SUPPORT_DENON = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE ) SUPPORT_MEDIA_MODES = ( - SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index 5675e573bc1..bb6e59053fb 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -3,7 +3,7 @@ "name": "Denon AVR Network Receivers", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/denonavr", - "requirements": ["denonavr==0.10.10"], + "requirements": ["denonavr==0.10.11"], "codeowners": ["@ol-iver", "@starkillerOG"], "ssdp": [ { diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index fb8c6fa3015..8d3102c441b 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -17,22 +17,13 @@ from denonavr.exceptions import ( ) import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -63,21 +54,21 @@ ATTR_SOUND_MODE_RAW = "sound_mode_raw" ATTR_DYNAMIC_EQ = "dynamic_eq" SUPPORT_DENON = ( - SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_VOLUME_SET + MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.VOLUME_SET ) SUPPORT_MEDIA_MODES = ( - SUPPORT_PLAY_MEDIA - | SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_VOLUME_SET - | SUPPORT_PLAY + MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.PLAY ) SCAN_INTERVAL = timedelta(seconds=10) @@ -167,7 +158,8 @@ class DenonDevice(MediaPlayerEntity): self._supported_features_base = SUPPORT_DENON self._supported_features_base |= ( - self._receiver.support_sound_mode and SUPPORT_SELECT_SOUND_MODE + self._receiver.support_sound_mode + and MediaPlayerEntityFeature.SELECT_SOUND_MODE ) self._available = True diff --git a/homeassistant/components/denonavr/translations/bg.json b/homeassistant/components/denonavr/translations/bg.json index 6ec6215c6e1..4b4384d0bc9 100644 --- a/homeassistant/components/denonavr/translations/bg.json +++ b/homeassistant/components/denonavr/translations/bg.json @@ -8,6 +8,9 @@ "user": { "data": { "host": "IP \u0430\u0434\u0440\u0435\u0441" + }, + "data_description": { + "host": "\u041e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0437\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435" } } } diff --git a/homeassistant/components/denonavr/translations/ca.json b/homeassistant/components/denonavr/translations/ca.json index f499c6a0a17..11065514ce4 100644 --- a/homeassistant/components/denonavr/translations/ca.json +++ b/homeassistant/components/denonavr/translations/ca.json @@ -27,6 +27,9 @@ "data": { "host": "Adre\u00e7a IP" }, + "data_description": { + "host": "Deixeu-ho en blanc per utilitzar descobriment autom\u00e0tic" + }, "description": "Connecta el teu receptor, si no es configura l'adre\u00e7a IP, s'utilitza el descobriment autom\u00e0tic", "title": "Receptors de xarxa AVR de Denon" } diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json index 414bd798f62..1c9a1a8ec95 100644 --- a/homeassistant/components/denonavr/translations/de.json +++ b/homeassistant/components/denonavr/translations/de.json @@ -27,6 +27,9 @@ "data": { "host": "IP-Adresse" }, + "data_description": { + "host": "Leer lassen, um automatische Erkennung zu verwenden" + }, "description": "Verbinde dich mit deinem Receiver, wenn die IP-Adresse nicht eingestellt ist, wird die automatische Erkennung verwendet", "title": "Denon AVR-Netzwerk-Receiver" } diff --git a/homeassistant/components/denonavr/translations/el.json b/homeassistant/components/denonavr/translations/el.json index 180cacc2459..d176fd944d6 100644 --- a/homeassistant/components/denonavr/translations/el.json +++ b/homeassistant/components/denonavr/translations/el.json @@ -27,6 +27,9 @@ "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" }, + "data_description": { + "host": "\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7" + }, "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b4\u03ad\u03ba\u03c4\u03b7 \u03c3\u03b1\u03c2, \u03b5\u03ac\u03bd \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7.", "title": "\u0394\u03ad\u03ba\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/en.json b/homeassistant/components/denonavr/translations/en.json index 383ae5a502c..2d937856b1c 100644 --- a/homeassistant/components/denonavr/translations/en.json +++ b/homeassistant/components/denonavr/translations/en.json @@ -27,6 +27,9 @@ "data": { "host": "IP Address" }, + "data_description": { + "host": "Leave blank to use auto-discovery" + }, "description": "Connect to your receiver, if the IP address is not set, auto-discovery is used", "title": "Denon AVR Network Receivers" } diff --git a/homeassistant/components/denonavr/translations/et.json b/homeassistant/components/denonavr/translations/et.json index 5dc9f3cc771..f133aaf9dd7 100644 --- a/homeassistant/components/denonavr/translations/et.json +++ b/homeassistant/components/denonavr/translations/et.json @@ -27,6 +27,9 @@ "data": { "host": "IP aadress" }, + "data_description": { + "host": "Automaatse avastamise kasutamiseks j\u00e4ta v\u00e4li t\u00fchjaks." + }, "description": "Kui IP-aadressi pole m\u00e4\u00e4ratud, kasutatakse automaatset avastamist", "title": "" } diff --git a/homeassistant/components/denonavr/translations/fr.json b/homeassistant/components/denonavr/translations/fr.json index 474f02f5e21..27a72477164 100644 --- a/homeassistant/components/denonavr/translations/fr.json +++ b/homeassistant/components/denonavr/translations/fr.json @@ -27,6 +27,9 @@ "data": { "host": "Adresse IP" }, + "data_description": { + "host": "Laissez le champ vide pour utiliser la d\u00e9couverte automatique" + }, "description": "Connectez-vous \u00e0 votre r\u00e9cepteur, si l'adresse IP n'est pas d\u00e9finie, la d\u00e9tection automatique est utilis\u00e9e", "title": "R\u00e9cepteurs r\u00e9seaux Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/hu.json b/homeassistant/components/denonavr/translations/hu.json index 874f190ff01..6891d18a9c4 100644 --- a/homeassistant/components/denonavr/translations/hu.json +++ b/homeassistant/components/denonavr/translations/hu.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Nem siker\u00fclt csatlakozni, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja \u00fajra. A h\u00e1l\u00f3zati \u00e9s Ethernet k\u00e1belek kih\u00faz\u00e1sa \u00e9s \u00fajracsatlakoztat\u00e1sa seg\u00edthet", - "not_denonavr_manufacturer": "Nem egy Denon AVR h\u00e1l\u00f3zati vev\u0151, felfedezett gy\u00e1rt\u00f3 nem egyezik", + "not_denonavr_manufacturer": "Nem egy Denon AVR h\u00e1l\u00f3zati vev\u0151, a felfedezett gy\u00e1rt\u00f3n\u00e9v nem megfelel\u0151", "not_denonavr_missing": "Nem Denon AVR h\u00e1l\u00f3zati vev\u0151, a felfedez\u00e9si inform\u00e1ci\u00f3k nem teljesek" }, "error": { @@ -27,6 +27,9 @@ "data": { "host": "IP c\u00edm" }, + "data_description": { + "host": "Az automatikus felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz hagyja \u00fcresen" + }, "description": "Csatlakozzon a vev\u0151h\u00f6z, ha az IP-c\u00edm nincs be\u00e1ll\u00edtva, az automatikus felder\u00edt\u00e9st haszn\u00e1lja", "title": "Denon AVR h\u00e1l\u00f3zati vev\u0151k\u00e9sz\u00fcl\u00e9kek" } diff --git a/homeassistant/components/denonavr/translations/id.json b/homeassistant/components/denonavr/translations/id.json index b2543d1d877..50a28ef5447 100644 --- a/homeassistant/components/denonavr/translations/id.json +++ b/homeassistant/components/denonavr/translations/id.json @@ -27,6 +27,9 @@ "data": { "host": "Alamat IP" }, + "data_description": { + "host": "Kosongkan untuk menggunakan penemuan otomatis" + }, "description": "Hubungkan ke Receiver Anda. Jika alamat IP tidak ditentukan, penemuan otomatis akan digunakan", "title": "Network Receiver Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/it.json b/homeassistant/components/denonavr/translations/it.json index 76d0d627ca3..23fffd3ab44 100644 --- a/homeassistant/components/denonavr/translations/it.json +++ b/homeassistant/components/denonavr/translations/it.json @@ -27,6 +27,9 @@ "data": { "host": "Indirizzo IP" }, + "data_description": { + "host": "Lascia vuoto per usare il rilevamento automatico" + }, "description": "Collega il ricevitore, se l'indirizzo IP non \u00e8 impostato, sar\u00e0 utilizzato il rilevamento automatico", "title": "Ricevitori di rete Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/ja.json b/homeassistant/components/denonavr/translations/ja.json index 1a5b41a3368..cd3198b7e10 100644 --- a/homeassistant/components/denonavr/translations/ja.json +++ b/homeassistant/components/denonavr/translations/ja.json @@ -27,6 +27,9 @@ "data": { "host": "IP\u30a2\u30c9\u30ec\u30b9" }, + "data_description": { + "host": "\u81ea\u52d5\u691c\u51fa\u3092\u4f7f\u7528\u3059\u308b\u306b\u306f\u3001\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059" + }, "description": "\u53d7\u4fe1\u6a5f\u306b\u63a5\u7d9a\u3057\u307e\u3059\u3002IP\u30a2\u30c9\u30ec\u30b9\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u81ea\u52d5\u691c\u51fa\u304c\u4f7f\u7528\u3055\u308c\u307e\u3059", "title": "\u30c7\u30ce\u30f3(Denon)AVR\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30ec\u30b7\u30fc\u30d0\u30fc" } diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index 47da10106f3..f03895452df 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -27,6 +27,9 @@ "data": { "host": "IP-adres" }, + "data_description": { + "host": "Leeg laten om auto-discovery te gebruiken" + }, "description": "Maak verbinding met uw ontvanger. Als het IP-adres niet is ingesteld, wordt automatische detectie gebruikt", "title": "Denon AVR Netwerk Ontvangers" } diff --git a/homeassistant/components/denonavr/translations/no.json b/homeassistant/components/denonavr/translations/no.json index 892cf8d5767..333c55a44f7 100644 --- a/homeassistant/components/denonavr/translations/no.json +++ b/homeassistant/components/denonavr/translations/no.json @@ -27,6 +27,9 @@ "data": { "host": "IP adresse" }, + "data_description": { + "host": "La feltet st\u00e5 tomt hvis du vil bruke automatisk s\u00f8k" + }, "description": "Koble til mottakeren, hvis IP-adressen ikke er angitt, brukes automatisk oppdagelse", "title": "Denon AVR Network Receivers" } diff --git a/homeassistant/components/denonavr/translations/pl.json b/homeassistant/components/denonavr/translations/pl.json index 6b09baf7d4c..054371c6ba1 100644 --- a/homeassistant/components/denonavr/translations/pl.json +++ b/homeassistant/components/denonavr/translations/pl.json @@ -27,6 +27,9 @@ "data": { "host": "Adres IP" }, + "data_description": { + "host": "Pozostaw puste, aby u\u017cy\u0107 automatycznego wykrywania" + }, "description": "\u0141\u0105czenie z urz\u0105dzeniem, je\u015bli adres IP nie jest zdefiniowany, u\u017cywane jest automatyczne wykrywanie.", "title": "Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/pt-BR.json b/homeassistant/components/denonavr/translations/pt-BR.json index 084c7dd3c18..2a716dacdca 100644 --- a/homeassistant/components/denonavr/translations/pt-BR.json +++ b/homeassistant/components/denonavr/translations/pt-BR.json @@ -27,6 +27,9 @@ "data": { "host": "Endere\u00e7o IP" }, + "data_description": { + "host": "Deixe em branco para usar a descoberta autom\u00e1tica" + }, "description": "Conecte-se ao seu receptor, se o endere\u00e7o IP n\u00e3o estiver definido, a descoberta autom\u00e1tica ser\u00e1 usada", "title": "Receptores de rede Denon AVR" } diff --git a/homeassistant/components/denonavr/translations/ru.json b/homeassistant/components/denonavr/translations/ru.json index c1fb25a9889..1db49decaad 100644 --- a/homeassistant/components/denonavr/translations/ru.json +++ b/homeassistant/components/denonavr/translations/ru.json @@ -27,6 +27,9 @@ "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441" }, + "data_description": { + "host": "\u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435" + }, "description": "\u0415\u0441\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d, \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435", "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" } diff --git a/homeassistant/components/denonavr/translations/tr.json b/homeassistant/components/denonavr/translations/tr.json index 2c32de293b8..046e535c621 100644 --- a/homeassistant/components/denonavr/translations/tr.json +++ b/homeassistant/components/denonavr/translations/tr.json @@ -27,6 +27,9 @@ "data": { "host": "IP Adresi" }, + "data_description": { + "host": "Otomatik bulmay\u0131 kullanmak i\u00e7in bo\u015f b\u0131rak\u0131n" + }, "description": "Al\u0131c\u0131n\u0131za ba\u011flan\u0131n, IP adresi ayarlanmazsa otomatik bulma kullan\u0131l\u0131r", "title": "Denon AVR A\u011f Al\u0131c\u0131lar\u0131" } diff --git a/homeassistant/components/denonavr/translations/zh-Hant.json b/homeassistant/components/denonavr/translations/zh-Hant.json index 073de46866d..1217a6d7b87 100644 --- a/homeassistant/components/denonavr/translations/zh-Hant.json +++ b/homeassistant/components/denonavr/translations/zh-Hant.json @@ -27,6 +27,9 @@ "data": { "host": "IP \u4f4d\u5740" }, + "data_description": { + "host": "\u4fdd\u6301\u7a7a\u767d\u4ee5\u4f7f\u7528\u81ea\u52d5\u641c\u7d22" + }, "description": "\u9023\u7dda\u81f3\u63a5\u6536\u5668\u3002\u5047\u5982\u672a\u8a2d\u5b9a IP \u4f4d\u5740\uff0c\u5c07\u4f7f\u7528\u81ea\u52d5\u63a2\u7d22\u3002", "title": "Denon AVR \u7db2\u8def\u63a5\u6536\u5668" } diff --git a/homeassistant/components/derivative/config_flow.py b/homeassistant/components/derivative/config_flow.py index 6249e9fd1cc..eea2b303a12 100644 --- a/homeassistant/components/derivative/config_flow.py +++ b/homeassistant/components/derivative/config_flow.py @@ -9,7 +9,6 @@ import voluptuous as vol from homeassistant.const import ( CONF_NAME, CONF_SOURCE, - CONF_UNIT_OF_MEASUREMENT, TIME_DAYS, TIME_HOURS, TIME_MINUTES, @@ -31,50 +30,48 @@ from .const import ( ) UNIT_PREFIXES = [ - {"value": "none", "label": "none"}, - {"value": "n", "label": "n (nano)"}, - {"value": "µ", "label": "µ (micro)"}, - {"value": "m", "label": "m (milli)"}, - {"value": "k", "label": "k (kilo)"}, - {"value": "M", "label": "M (mega)"}, - {"value": "G", "label": "G (giga)"}, - {"value": "T", "label": "T (tera)"}, - {"value": "P", "label": "P (peta)"}, + selector.SelectOptionDict(value="none", label="none"), + selector.SelectOptionDict(value="n", label="n (nano)"), + selector.SelectOptionDict(value="µ", label="µ (micro)"), + selector.SelectOptionDict(value="m", label="m (milli)"), + selector.SelectOptionDict(value="k", label="k (kilo)"), + selector.SelectOptionDict(value="M", label="M (mega)"), + selector.SelectOptionDict(value="G", label="G (giga)"), + selector.SelectOptionDict(value="T", label="T (tera)"), + selector.SelectOptionDict(value="P", label="P (peta)"), ] TIME_UNITS = [ - {"value": TIME_SECONDS, "label": "Seconds"}, - {"value": TIME_MINUTES, "label": "Minutes"}, - {"value": TIME_HOURS, "label": "Hours"}, - {"value": TIME_DAYS, "label": "Days"}, + selector.SelectOptionDict(value=TIME_SECONDS, label="Seconds"), + selector.SelectOptionDict(value=TIME_MINUTES, label="Minutes"), + selector.SelectOptionDict(value=TIME_HOURS, label="Hours"), + selector.SelectOptionDict(value=TIME_DAYS, label="Days"), ] OPTIONS_SCHEMA = vol.Schema( { - vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector( - { - "number": { - "min": 0, - "max": 6, - "mode": "box", - CONF_UNIT_OF_MEASUREMENT: "decimals", - } - } + vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=6, + mode=selector.NumberSelectorMode.BOX, + unit_of_measurement="decimals", + ), ), - vol.Required(CONF_TIME_WINDOW): selector.selector({"duration": {}}), - vol.Required(CONF_UNIT_PREFIX, default="none"): selector.selector( - {"select": {"options": UNIT_PREFIXES}} + vol.Required(CONF_TIME_WINDOW): selector.DurationSelector(), + vol.Required(CONF_UNIT_PREFIX, default="none"): selector.SelectSelector( + selector.SelectSelectorConfig(options=UNIT_PREFIXES), ), - vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.selector( - {"select": {"options": TIME_UNITS}} + vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.SelectSelector( + selector.SelectSelectorConfig(options=TIME_UNITS), ), } ) CONFIG_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): selector.selector({"text": {}}), - vol.Required(CONF_SOURCE): selector.selector( - {"entity": {"domain": "sensor"}}, + vol.Required(CONF_NAME): selector.TextSelector(), + vol.Required(CONF_SOURCE): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor"), ), } ).extend(OPTIONS_SCHEMA.schema) diff --git a/homeassistant/components/derivative/translations/bg.json b/homeassistant/components/derivative/translations/bg.json new file mode 100644 index 00000000000..14946d95fe0 --- /dev/null +++ b/homeassistant/components/derivative/translations/bg.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u0418\u043c\u0435" + } + }, + "options": { + "data": { + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/ca.json b/homeassistant/components/derivative/translations/ca.json new file mode 100644 index 00000000000..3003b9349fd --- /dev/null +++ b/homeassistant/components/derivative/translations/ca.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nom", + "round": "Precisi\u00f3", + "source": "Sensor d'entrada", + "time_window": "Finestra de temps", + "unit_prefix": "Prefix m\u00e8tric", + "unit_time": "Unitat de temps" + }, + "data_description": { + "round": "Controla el nombre de d\u00edgits decimals a la sortida.", + "time_window": "Si s'estableix, el valor del sensor \u00e9s una mitjana m\u00f2bil ponderada en el temps de les derivades dins d'aquesta finestra.", + "unit_prefix": "La sortida s'escalar\u00e0 segons el prefix m\u00e8tric i la unitat de temps de la derivada seleccionats." + }, + "description": "Crea un sensor que estima la derivada d'un altre sensor.", + "title": "Afegeix sensor derivatiu" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nom", + "round": "Precisi\u00f3", + "source": "Sensor d'entrada", + "time_window": "Finestra de temps", + "unit_prefix": "Prefix m\u00e8tric", + "unit_time": "Unitat de temps" + }, + "data_description": { + "round": "Controla el nombre de d\u00edgits decimals a la sortida.", + "time_window": "Si s'estableix, el valor del sensor \u00e9s una mitjana m\u00f2bil ponderada en el temps de les derivades dins d'aquesta finestra.", + "unit_prefix": "La sortida s'escalar\u00e0 segons el prefix m\u00e8tric i la unitat de temps de la derivada seleccionats." + } + }, + "options": { + "data": { + "name": "Nom", + "round": "Precisi\u00f3", + "source": "Sensor d'entrada", + "time_window": "Finestra de temps", + "unit_prefix": "Prefix m\u00e8tric", + "unit_time": "Unitat de temps" + }, + "data_description": { + "round": "Controla el nombre de d\u00edgits decimals a la sortida.", + "time_window": "Si s'estableix, el valor del sensor \u00e9s una mitjana m\u00f2bil ponderada en el temps de les derivades dins d'aquesta finestra.", + "unit_prefix": "La sortida s'escalar\u00e0 segons el prefix m\u00e8tric i la unitat de temps de la derivada seleccionats." + }, + "description": "Crea un sensor que estima la derivada d'un altre sensor." + } + } + }, + "title": "Sensor derivatiu" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/cs.json b/homeassistant/components/derivative/translations/cs.json new file mode 100644 index 00000000000..ee8ee0e6d86 --- /dev/null +++ b/homeassistant/components/derivative/translations/cs.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data_description": { + "unit_prefix": "." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/de.json b/homeassistant/components/derivative/translations/de.json new file mode 100644 index 00000000000..4e1dbc1929c --- /dev/null +++ b/homeassistant/components/derivative/translations/de.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Name", + "round": "Genauigkeit", + "source": "Eingangssensor", + "time_window": "Zeitfenster", + "unit_prefix": "Metrisches Pr\u00e4fix", + "unit_time": "Zeiteinheit" + }, + "data_description": { + "round": "Steuert die Anzahl der Dezimalstellen in der Ausgabe.", + "time_window": "Wenn gesetzt, ist der Sensorwert ein zeitgewichteter gleitender Durchschnitt von Ableitungen innerhalb dieses Fensters.", + "unit_prefix": "Die Ausgabe wird gem\u00e4\u00df dem ausgew\u00e4hlten metrischen Pr\u00e4fix und der Zeiteinheit der Ableitung skaliert." + }, + "description": "Erstelle einen Sensor, der die Ableitung eines Sensors sch\u00e4tzt.", + "title": "Ableitungssensor hinzuf\u00fcgen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Name", + "round": "Genauigkeit", + "source": "Eingangssensor", + "time_window": "Zeitfenster", + "unit_prefix": "Metrisches Pr\u00e4fix", + "unit_time": "Zeiteinheit" + }, + "data_description": { + "round": "Steuert die Anzahl der Dezimalstellen in der Ausgabe.", + "time_window": "Wenn gesetzt, ist der Sensorwert ein zeitgewichteter gleitender Durchschnitt von Ableitungen innerhalb dieses Fensters.", + "unit_prefix": "Die Ausgabe wird gem\u00e4\u00df dem ausgew\u00e4hlten metrischen Pr\u00e4fix und der Zeiteinheit der Ableitung skaliert.." + } + }, + "options": { + "data": { + "name": "Name", + "round": "Genauigkeit", + "source": "Eingangssensor", + "time_window": "Zeitfenster", + "unit_prefix": "Metrisches Pr\u00e4fix", + "unit_time": "Zeiteinheit" + }, + "data_description": { + "round": "Steuert die Anzahl der Dezimalstellen in der Ausgabe.", + "time_window": "Wenn gesetzt, ist der Sensorwert ein zeitgewichteter gleitender Durchschnitt von Ableitungen innerhalb dieses Fensters.", + "unit_prefix": "Die Ausgabe wird gem\u00e4\u00df dem ausgew\u00e4hlten metrischen Pr\u00e4fix und der Zeiteinheit der Ableitung skaliert.." + }, + "description": "Erstelle einen Sensor, der die Ableitung eines Sensors sch\u00e4tzt." + } + } + }, + "title": "Ableitungssensor" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/el.json b/homeassistant/components/derivative/translations/el.json new file mode 100644 index 00000000000..a5a19efc1e5 --- /dev/null +++ b/homeassistant/components/derivative/translations/el.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "round": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "source": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "time_window": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf", + "unit_prefix": "\u039c\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1", + "unit_time": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5" + }, + "data_description": { + "round": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.", + "time_window": "\u0395\u03ac\u03bd \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ac \u03c3\u03c4\u03b1\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b9\u03bd\u03b7\u03c4\u03cc\u03c2 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03c9\u03bd \u03b5\u03bd\u03c4\u03cc\u03c2 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b8\u03cd\u03c1\u03bf\u03c5.", + "unit_prefix": "\u0397 \u03c0\u03b1\u03c1\u03ac\u03b3\u03c9\u03b3\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03c9\u03b8\u03b5\u03af \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03bf\u03c5." + }, + "description": "\u0397 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.\n\u0395\u03ac\u03bd \u03c4\u03bf \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 0, \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ac \u03c3\u03c4\u03b1\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b9\u03bd\u03b7\u03c4\u03cc\u03c2 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03c9\u03bd \u03b5\u03bd\u03c4\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b8\u03cd\u03c1\u03bf\u03c5.\n\u0397 \u03c0\u03b1\u03c1\u03ac\u03b3\u03c9\u03b3\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03ce\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03bf\u03c5.", + "title": "\u039d\u03ad\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 Derivative" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "round": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "source": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "time_window": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf", + "unit_prefix": "\u039c\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1", + "unit_time": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5" + }, + "data_description": { + "round": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.", + "time_window": "\u0395\u03ac\u03bd \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ac \u03c3\u03c4\u03b1\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b9\u03bd\u03b7\u03c4\u03cc\u03c2 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03c9\u03bd \u03b5\u03bd\u03c4\u03cc\u03c2 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b8\u03cd\u03c1\u03bf\u03c5.", + "unit_prefix": "\u0397 \u03c0\u03b1\u03c1\u03ac\u03b3\u03c9\u03b3\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03c9\u03b8\u03b5\u03af \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03bf\u03c5." + } + }, + "options": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "round": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "source": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "time_window": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf", + "unit_prefix": "\u039c\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1", + "unit_time": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5" + }, + "data_description": { + "round": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.", + "time_window": "\u0395\u03ac\u03bd \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af, \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ac \u03c3\u03c4\u03b1\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b9\u03bd\u03b7\u03c4\u03cc\u03c2 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03c9\u03bd \u03b5\u03bd\u03c4\u03cc\u03c2 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b8\u03cd\u03c1\u03bf\u03c5.", + "unit_prefix": "\u0397 \u03c0\u03b1\u03c1\u03ac\u03b3\u03c9\u03b3\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03c9\u03b8\u03b5\u03af \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03bf\u03c5." + }, + "description": "\u0397 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.\n\u0395\u03ac\u03bd \u03c4\u03bf \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 0, \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03bd\u03b1\u03c2 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ac \u03c3\u03c4\u03b1\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03b9\u03bd\u03b7\u03c4\u03cc\u03c2 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03c9\u03bd \u03b5\u03bd\u03c4\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b8\u03cd\u03c1\u03bf\u03c5.\n\u0397 \u03c0\u03b1\u03c1\u03ac\u03b3\u03c9\u03b3\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03ce\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03bf\u03c5." + } + } + }, + "title": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c0\u03b1\u03c1\u03b1\u03b3\u03ce\u03b3\u03c9\u03bd" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/en.json b/homeassistant/components/derivative/translations/en.json index b91318b5237..884f1fa5244 100644 --- a/homeassistant/components/derivative/translations/en.json +++ b/homeassistant/components/derivative/translations/en.json @@ -36,6 +36,22 @@ "time_window": "If set, the sensor's value is a time weighted moving average of derivatives within this window.", "unit_prefix": "The output will be scaled according to the selected metric prefix and time unit of the derivative.." } + }, + "options": { + "data": { + "name": "Name", + "round": "Precision", + "source": "Input sensor", + "time_window": "Time window", + "unit_prefix": "Metric prefix", + "unit_time": "Time unit" + }, + "data_description": { + "round": "Controls the number of decimal digits in the output.", + "time_window": "If set, the sensor's value is a time weighted moving average of derivatives within this window.", + "unit_prefix": "The output will be scaled according to the selected metric prefix and time unit of the derivative.." + }, + "description": "Create a sensor that estimates the derivative of a sensor." } } }, diff --git a/homeassistant/components/derivative/translations/et.json b/homeassistant/components/derivative/translations/et.json new file mode 100644 index 00000000000..45c566fac9b --- /dev/null +++ b/homeassistant/components/derivative/translations/et.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nimi", + "round": "T\u00e4psus", + "source": "Sisendandur", + "time_window": "Ajavahemik", + "unit_prefix": "M\u00f5\u00f5diku eesliide", + "unit_time": "Aja\u00fchik" + }, + "data_description": { + "round": "K\u00fcmnendkohtade arv v\u00e4ljundis.", + "time_window": "Kui see on m\u00e4\u00e4ratud on anduri v\u00e4\u00e4rtus selle akna tuletisinstrumentide ajaga kaalutud liikuv keskmine.", + "unit_prefix": "Tuletis skaleeritakse vastavalt valitud meetrilisele eesliitele ja tuletise aja\u00fchikule." + }, + "description": "Loo andur mis hindab anduri tuletist.", + "title": "Lisa uus tuletisandur" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nimi", + "round": "T\u00e4psus", + "source": "Sisendandur", + "time_window": "Ajavahemik", + "unit_prefix": "M\u00f5\u00f5diku eesliide", + "unit_time": "Aja\u00fchik" + }, + "data_description": { + "round": "K\u00fcmnendkohtade arv v\u00e4ljundis.", + "time_window": "Kui see on m\u00e4\u00e4ratud on anduri v\u00e4\u00e4rtus selle akna tuletisinstrumentide ajaga kaalutud liikuv keskmine.", + "unit_prefix": "Tuletis skaleeritakse vastavalt valitud meetrilisele eesliitele ja tuletise aja\u00fchikule." + } + }, + "options": { + "data": { + "name": "Nimi", + "round": "T\u00e4psus", + "source": "Sisendandur", + "time_window": "Ajavahemik", + "unit_prefix": "M\u00f5\u00f5diku eesliide", + "unit_time": "Aja\u00fchik" + }, + "data_description": { + "round": "K\u00fcmnendkohtade arv v\u00e4ljundis.", + "time_window": "Kui see on m\u00e4\u00e4ratud, on anduri v\u00e4\u00e4rtus selle akna tuletisinstrumentide ajaga kaalutud liikuv keskmine.", + "unit_prefix": "Tuletis skaleeritakse vastavalt valitud meetrilisele eesliitele ja tuletise aja\u00fchikule." + }, + "description": "T\u00e4psus reguleerib k\u00fcmnendkohtade arvu v\u00e4ljundis.\n Kui ajaaken ei ole 0, on anduri v\u00e4\u00e4rtuseks aknas olevate tuletisinstrumentide ajaga kaalutud liikuv keskmine.\n Tuletist skaleeritakse vastavalt valitud meetrilise prefiksile ja tuletise aja\u00fchikule." + } + } + }, + "title": "Tuletisandur" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/fr.json b/homeassistant/components/derivative/translations/fr.json new file mode 100644 index 00000000000..d967a3abc89 --- /dev/null +++ b/homeassistant/components/derivative/translations/fr.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nom", + "round": "Pr\u00e9cision", + "source": "Capteur d'entr\u00e9e", + "time_window": "P\u00e9riode", + "unit_prefix": "Pr\u00e9fixe m\u00e9trique", + "unit_time": "Unit\u00e9 de temps" + }, + "data_description": { + "round": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux dans la sortie.", + "time_window": "Si d\u00e9finie, la valeur du capteur est une moyenne mobile pond\u00e9r\u00e9e dans le temps des d\u00e9riv\u00e9es dans cette p\u00e9riode.", + "unit_prefix": "La sortie sera mise \u00e0 l'\u00e9chelle en fonction du pr\u00e9fixe m\u00e9trique et de l'unit\u00e9 de temps de la d\u00e9riv\u00e9e s\u00e9lectionn\u00e9s." + }, + "description": "Cr\u00e9ez un capteur calculant la d\u00e9riv\u00e9e d'un autre capteur.", + "title": "Ajouter un capteur de d\u00e9riv\u00e9e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nom", + "round": "Pr\u00e9cision", + "source": "Capteur d'entr\u00e9e", + "time_window": "P\u00e9riode", + "unit_prefix": "Pr\u00e9fixe m\u00e9trique", + "unit_time": "Unit\u00e9 de temps" + }, + "data_description": { + "round": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux dans la sortie.", + "time_window": "Si d\u00e9finie, la valeur du capteur est une moyenne mobile pond\u00e9r\u00e9e dans le temps des d\u00e9riv\u00e9es dans cette p\u00e9riode.", + "unit_prefix": "La sortie sera mise \u00e0 l'\u00e9chelle en fonction du pr\u00e9fixe m\u00e9trique et de l'unit\u00e9 de temps de la d\u00e9riv\u00e9e s\u00e9lectionn\u00e9s.." + } + }, + "options": { + "data": { + "name": "Nom", + "round": "Pr\u00e9cision", + "source": "Capteur d'entr\u00e9e", + "time_window": "P\u00e9riode", + "unit_prefix": "Pr\u00e9fixe m\u00e9trique", + "unit_time": "Unit\u00e9 de temps" + }, + "data_description": { + "round": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux dans la sortie.", + "time_window": "Si d\u00e9finie, la valeur du capteur est une moyenne mobile pond\u00e9r\u00e9e dans le temps des d\u00e9riv\u00e9es dans cette p\u00e9riode.", + "unit_prefix": "La sortie sera mise \u00e0 l'\u00e9chelle en fonction du pr\u00e9fixe m\u00e9trique et de l'unit\u00e9 de temps de la d\u00e9riv\u00e9e s\u00e9lectionn\u00e9s." + }, + "description": "Cr\u00e9ez un capteur calculant la d\u00e9riv\u00e9e d'un autre capteur." + } + } + }, + "title": "Capteur de d\u00e9riv\u00e9e" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/he.json b/homeassistant/components/derivative/translations/he.json new file mode 100644 index 00000000000..317b836da6d --- /dev/null +++ b/homeassistant/components/derivative/translations/he.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "options": { + "data_description": { + "unit_prefix": "." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/hu.json b/homeassistant/components/derivative/translations/hu.json new file mode 100644 index 00000000000..e71175d2a32 --- /dev/null +++ b/homeassistant/components/derivative/translations/hu.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Elnevez\u00e9s", + "round": "Pontoss\u00e1g", + "source": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "time_window": "Id\u0151ablak", + "unit_prefix": "M\u00e9rt\u00e9kegys\u00e9g el\u0151tag", + "unit_time": "Id\u0151egys\u00e9g" + }, + "data_description": { + "round": "Az eredm\u00e9ny tizedesjegyeinek sz\u00e1ma.", + "time_window": "Ha be van \u00e1ll\u00edtva, az \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke az ablakon bel\u00fcli sz\u00e1rmaz\u00e9kok id\u0151vel s\u00falyozott mozg\u00f3\u00e1tlaga.", + "unit_prefix": "A sz\u00e1rmaz\u00e9kos \u00e9rt\u00e9k a sz\u00e1rmaztatott term\u00e9k kiv\u00e1lasztott m\u00e9rt\u00e9kegys\u00e9g el\u0151tagja \u00e9s id\u0151egys\u00e9ge szerint lesz sk\u00e1l\u00e1zva." + }, + "description": "Hozzon l\u00e9tre egy \u00e9rz\u00e9kel\u0151t, amely megbecs\u00fcli a forr\u00e1s \u00e9rz\u00e9kel\u0151 sz\u00e1rmaz\u00e9k\u00e1t.", + "title": "\u00daj sz\u00e1rmaz\u00e9kos \u00e9rz\u00e9kel\u0151" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Elnevez\u00e9s", + "round": "Pontoss\u00e1g", + "source": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "time_window": "Id\u0151ablak", + "unit_prefix": "M\u00e9rt\u00e9kegys\u00e9g el\u0151tag", + "unit_time": "Id\u0151egys\u00e9g" + }, + "data_description": { + "round": "Az eredm\u00e9ny tizedesjegyeinek sz\u00e1ma.", + "time_window": "Ha be van \u00e1ll\u00edtva, az \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke az ablakon bel\u00fcli sz\u00e1rmaz\u00e9kok id\u0151vel s\u00falyozott mozg\u00f3\u00e1tlaga.", + "unit_prefix": "A sz\u00e1rmaz\u00e9kos \u00e9rt\u00e9k a sz\u00e1rmaztatott term\u00e9k kiv\u00e1lasztott m\u00e9rt\u00e9kegys\u00e9g el\u0151tagja \u00e9s id\u0151egys\u00e9ge szerint lesz sk\u00e1l\u00e1zva.." + } + }, + "options": { + "data": { + "name": "Elnevez\u00e9s", + "round": "Pontoss\u00e1g", + "source": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "time_window": "Id\u0151ablak", + "unit_prefix": "M\u00e9rt\u00e9kegys\u00e9g el\u0151tag", + "unit_time": "Id\u0151egys\u00e9g" + }, + "data_description": { + "round": "Az eredm\u00e9ny tizedesjegyeinek sz\u00e1ma.", + "time_window": "Ha be van \u00e1ll\u00edtva, az \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke az ablakon bel\u00fcli sz\u00e1rmaz\u00e9kok id\u0151vel s\u00falyozott mozg\u00f3\u00e1tlaga.", + "unit_prefix": "A sz\u00e1rmaz\u00e9kos \u00e9rt\u00e9k a sz\u00e1rmaztatott term\u00e9k kiv\u00e1lasztott m\u00e9rt\u00e9kegys\u00e9g el\u0151tagja \u00e9s id\u0151egys\u00e9ge szerint lesz sk\u00e1l\u00e1zva.." + }, + "description": "Hozzon l\u00e9tre egy \u00e9rz\u00e9kel\u0151t, amely megbecs\u00fcli a forr\u00e1s \u00e9rz\u00e9kel\u0151 sz\u00e1rmaz\u00e9k\u00e1t." + } + } + }, + "title": "Sz\u00e1rmaz\u00e9kos \u00e9rz\u00e9kel\u0151" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/id.json b/homeassistant/components/derivative/translations/id.json new file mode 100644 index 00000000000..67d6752a182 --- /dev/null +++ b/homeassistant/components/derivative/translations/id.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nama", + "round": "Presisi", + "source": "Sensor input", + "time_window": "Jangka waktu", + "unit_prefix": "Prefiks metrik", + "unit_time": "Unit waktu" + }, + "data_description": { + "round": "Mengontrol jumlah digit desimal dalam output.", + "time_window": "Jika disetel, nilai sensor adalah rata-rata bergerak berbobot waktu dari turunan dalam jangka ini.", + "unit_prefix": "Output akan diskalakan sesuai dengan prefiks metrik yang dipilih dan unit waktu turunan." + }, + "description": "Buat sensor yang memperkirakan nilai turunan dari sebuah sensor.", + "title": "Tambahkan sensor Turunan" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nama", + "round": "Presisi", + "source": "Sensor input", + "time_window": "Jangka waktu", + "unit_prefix": "Prefiks metrik", + "unit_time": "Unit waktu" + }, + "data_description": { + "round": "Mengontrol jumlah digit desimal dalam output.", + "time_window": "Jika disetel, nilai sensor adalah rata-rata bergerak berbobot waktu dari turunan dalam jangka ini.", + "unit_prefix": "Output akan diskalakan sesuai dengan prefiks metrik yang dipilih dan unit waktu turunan.." + } + }, + "options": { + "data": { + "name": "Nama", + "round": "Presisi", + "source": "Sensor input", + "time_window": "Jangka waktu", + "unit_prefix": "Prefiks metrik", + "unit_time": "Unit waktu" + }, + "data_description": { + "round": "Mengontrol jumlah digit desimal dalam output.", + "time_window": "Jika disetel, nilai sensor adalah rata-rata bergerak berbobot waktu dari turunan dalam jangka ini.", + "unit_prefix": "Output akan diskalakan sesuai dengan prefiks metrik yang dipilih dan unit waktu turunan.." + }, + "description": "Buat sensor yang memperkirakan nilai turunan dari sebuah sensor." + } + } + }, + "title": "Sensor Turunan" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/it.json b/homeassistant/components/derivative/translations/it.json new file mode 100644 index 00000000000..ad888e13745 --- /dev/null +++ b/homeassistant/components/derivative/translations/it.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nome", + "round": "Precisione", + "source": "Sensore di ingresso", + "time_window": "Finestra temporale", + "unit_prefix": "Prefisso metrico", + "unit_time": "Unit\u00e0 di tempo" + }, + "data_description": { + "round": "Controlla il numero di cifre decimali nell'uscita.", + "time_window": "Se impostato, il valore del sensore \u00e8 una media mobile delle derivate ponderata nel tempo all'interno di questa finestra.", + "unit_prefix": "L'output sar\u00e0 ridimensionato in base al prefisso metrico selezionato e all'unit\u00e0 di tempo della derivata." + }, + "description": "Crea un sensore che stimi la derivata di un sensore.", + "title": "Aggiungi sensore derivata" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nome", + "round": "Precisione", + "source": "Sensore di ingresso", + "time_window": "Finestra temporale", + "unit_prefix": "Prefisso metrico", + "unit_time": "Unit\u00e0 di tempo" + }, + "data_description": { + "round": "Controlla il numero di cifre decimali nell'uscita.", + "time_window": "Se impostato, il valore del sensore \u00e8 una media mobile delle derivate ponderata nel tempo all'interno di questa finestra.", + "unit_prefix": "L'output sar\u00e0 ridimensionato in base al prefisso metrico selezionato e all'unit\u00e0 di tempo della derivata.." + } + }, + "options": { + "data": { + "name": "Nome", + "round": "Precisione", + "source": "Sensore di ingresso", + "time_window": "Finestra temporale", + "unit_prefix": "Prefisso metrico", + "unit_time": "Unit\u00e0 di tempo" + }, + "data_description": { + "round": "Controlla il numero di cifre decimali nell'uscita.", + "time_window": "Se impostato, il valore del sensore \u00e8 una media mobile delle derivate ponderata nel tempo all'interno di questa finestra.", + "unit_prefix": "L'output sar\u00e0 ridimensionato in base al prefisso metrico selezionato e all'unit\u00e0 di tempo della derivata.." + }, + "description": "Crea un sensore che stimi la derivata di un sensore." + } + } + }, + "title": "Sensore derivata" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/ja.json b/homeassistant/components/derivative/translations/ja.json new file mode 100644 index 00000000000..10232f996d6 --- /dev/null +++ b/homeassistant/components/derivative/translations/ja.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u540d\u524d", + "round": "\u7cbe\u5ea6", + "source": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "time_window": "\u6642\u9593\u8ef8", + "unit_prefix": "\u30e1\u30c8\u30ea\u30c3\u30af\u63a5\u982d\u8f9e", + "unit_time": "\u6642\u9593\u5358\u4f4d" + }, + "data_description": { + "round": "\u51fa\u529b\u5024\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3002", + "time_window": "\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u308b\u5834\u5408\u3001\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u306f\u3053\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u5185\u306e\u5fae\u5206\u306e\u6642\u9593\u52a0\u91cd\u79fb\u52d5\u5e73\u5747\u3068\u306a\u308a\u307e\u3059\u3002", + "unit_prefix": "\u5fae\u5206\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3068\u5fae\u5206\u306e\u6642\u9593\u5358\u4f4d\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002" + }, + "description": "\u7cbe\u5ea6\u306f\u3001\u51fa\u529b\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002\n\u6642\u9593\u7a93\u304c0\u3067\u306a\u3044\u5834\u5408\u3001\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u306f\u7a93\u5185\u306e\u5fae\u5206\u306e\u6642\u9593\u52a0\u91cd\u79fb\u52d5\u5e73\u5747\u306b\u306a\u308a\u307e\u3059\u3002\n\u5fae\u5206\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9\u3068\u5fae\u5206\u306e\u6642\u9593\u5358\u4f4d\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002", + "title": "\u65b0\u3057\u3044\u6d3e\u751f(Derivative)\u30bb\u30f3\u30b5\u30fc" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u540d\u524d", + "round": "\u7cbe\u5ea6", + "source": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "time_window": "\u6642\u9593\u8ef8", + "unit_prefix": "\u30e1\u30c8\u30ea\u30c3\u30af\u63a5\u982d\u8f9e", + "unit_time": "\u6642\u9593\u5358\u4f4d" + }, + "data_description": { + "round": "\u51fa\u529b\u5024\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3002", + "time_window": "\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u308b\u5834\u5408\u3001\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u306f\u3053\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u5185\u306e\u5fae\u5206\u306e\u6642\u9593\u52a0\u91cd\u79fb\u52d5\u5e73\u5747\u3068\u306a\u308a\u307e\u3059\u3002", + "unit_prefix": "\u5fae\u5206\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3068\u5fae\u5206\u306e\u6642\u9593\u5358\u4f4d\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002." + } + }, + "options": { + "data": { + "name": "\u540d\u524d", + "round": "\u7cbe\u5ea6", + "source": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "time_window": "\u6642\u9593\u8ef8", + "unit_prefix": "\u30e1\u30c8\u30ea\u30c3\u30af\u63a5\u982d\u8f9e", + "unit_time": "\u6642\u9593\u5358\u4f4d" + }, + "data_description": { + "round": "\u51fa\u529b\u5024\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3002", + "time_window": "\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u308b\u5834\u5408\u3001\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u306f\u3053\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u5185\u306e\u5fae\u5206\u306e\u6642\u9593\u52a0\u91cd\u79fb\u52d5\u5e73\u5747\u3068\u306a\u308a\u307e\u3059\u3002", + "unit_prefix": "\u5fae\u5206\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3068\u5fae\u5206\u306e\u6642\u9593\u5358\u4f4d\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002." + }, + "description": "\u7cbe\u5ea6\u306f\u3001\u51fa\u529b\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002\n\u6642\u9593\u7a93\u304c0\u3067\u306a\u3044\u5834\u5408\u3001\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u306f\u7a93\u5185\u306e\u5fae\u5206\u306e\u6642\u9593\u52a0\u91cd\u79fb\u52d5\u5e73\u5747\u306b\u306a\u308a\u307e\u3059\u3002\n\u5fae\u5206\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9\u3068\u5fae\u5206\u306e\u6642\u9593\u5358\u4f4d\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002" + } + } + }, + "title": "\u6d3e\u751f(Derivative)\u30bb\u30f3\u30b5\u30fc" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/nl.json b/homeassistant/components/derivative/translations/nl.json new file mode 100644 index 00000000000..8b7cf5a9402 --- /dev/null +++ b/homeassistant/components/derivative/translations/nl.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Naam", + "round": "Precisie", + "source": "Invoer sensor", + "time_window": "Tijdsvenster", + "unit_prefix": "Metrisch voorvoegsel", + "unit_time": "Tijdseenheid" + }, + "data_description": { + "round": "Regelt het aantal decimale cijfers in de uitvoer.", + "time_window": "Indien ingesteld, is de waarde van de sensor een tijdgewogen voortschrijdend gemiddelde van de afgeleiden binnen dit venster.", + "unit_prefix": "De uitvoer wordt geschaald volgens het geselecteerde metrische voorvoegsel en de tijdseenheid van de afgeleide" + }, + "description": "Maak een sensor die de afgeleide van een sensor schat.", + "title": "Voeg afgeleide sensor toe" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Naam", + "round": "Precisie", + "source": "Invoer sensor", + "time_window": "Tijdsvenster", + "unit_prefix": "Metrisch voorvoegsel", + "unit_time": "Tijdseenheid" + }, + "data_description": { + "round": "Regelt het aantal decimale cijfers in de uitvoer.", + "time_window": "Indien ingesteld, is de waarde van de sensor een tijdgewogen voortschrijdend gemiddelde van de afgeleiden binnen dit venster.", + "unit_prefix": "De uitvoer wordt geschaald volgens het geselecteerde metrische voorvoegsel en de tijdseenheid van de afgeleide." + } + }, + "options": { + "data": { + "name": "Naam", + "round": "Precisie", + "source": "Invoer sensor", + "time_window": "Tijdsvenster", + "unit_prefix": "Metrisch voorvoegsel", + "unit_time": "Tijdseenheid" + }, + "data_description": { + "round": "Regelt het aantal decimale cijfers in de uitvoer.", + "time_window": "Indien ingesteld, is de waarde van de sensor een tijdgewogen voortschrijdend gemiddelde van de afgeleiden binnen dit venster.", + "unit_prefix": "De uitvoer wordt geschaald volgens het geselecteerde metrische voorvoegsel en de tijdseenheid van de afgeleide." + }, + "description": "Maak een sensor die de afgeleide van een sensor schat." + } + } + }, + "title": "Derivatieve sensor" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/no.json b/homeassistant/components/derivative/translations/no.json new file mode 100644 index 00000000000..735f1fae6ae --- /dev/null +++ b/homeassistant/components/derivative/translations/no.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Navn", + "round": "Presisjon", + "source": "Inngangssensor", + "time_window": "Tidsvindu", + "unit_prefix": "Metrisk prefiks", + "unit_time": "Tidsenhet" + }, + "data_description": { + "round": "Styrer antall desimaler i utdataene.", + "time_window": "Hvis den er angitt, er sensorens verdi et tidsvektet glidende gjennomsnitt av derivater i dette vinduet.", + "unit_prefix": "Utdataene skaleres i henhold til det valgte metriske prefikset og tidsenheten til den deriverte." + }, + "description": "Lag en sensor som estimerer den deriverte av en sensor.", + "title": "Legg til derivatsensor" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Navn", + "round": "Presisjon", + "source": "Inngangssensor", + "time_window": "Tidsvindu", + "unit_prefix": "Metrisk prefiks", + "unit_time": "Tidsenhet" + }, + "data_description": { + "round": "Styrer antall desimaler i utdataene.", + "time_window": "Hvis den er angitt, er sensorens verdi et tidsvektet glidende gjennomsnitt av derivater i dette vinduet.", + "unit_prefix": "Utdataene skaleres i henhold til det valgte metriske prefikset og tidsenheten til den deriverte.." + } + }, + "options": { + "data": { + "name": "Navn", + "round": "Presisjon", + "source": "Inngangssensor", + "time_window": "Tidsvindu", + "unit_prefix": "Metrisk prefiks", + "unit_time": "Tidsenhet" + }, + "data_description": { + "round": "Styrer antall desimaler i utdataene.", + "time_window": "Hvis den er angitt, er sensorens verdi et tidsvektet glidende gjennomsnitt av derivater i dette vinduet.", + "unit_prefix": "Utdataene skaleres i henhold til det valgte metriske prefikset og tidsenheten til den deriverte.." + }, + "description": "Lag en sensor som estimerer den deriverte av en sensor." + } + } + }, + "title": "Avledet sensor" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/pl.json b/homeassistant/components/derivative/translations/pl.json new file mode 100644 index 00000000000..041d52ffeff --- /dev/null +++ b/homeassistant/components/derivative/translations/pl.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nazwa", + "round": "Precyzja", + "source": "Sensor wej\u015bciowy", + "time_window": "Okno czasowe", + "unit_prefix": "Prefiks metryczny", + "unit_time": "Jednostka czasu" + }, + "data_description": { + "round": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych.", + "time_window": "Je\u015bli jest ustawiona, warto\u015b\u0107 sensora jest wa\u017con\u0105 w czasie \u015bredni\u0105 ruchom\u0105 pochodnych w tym oknie.", + "unit_prefix": "Wynik b\u0119dzie skalowany zgodnie z wybranym prefiksem metrycznym i jednostk\u0105 czasu pochodnej." + }, + "description": "Tworzy sensor, kt\u00f3ry szacuje pochodn\u0105 sensora.", + "title": "Dodaj sensor pochodnej" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nazwa", + "round": "Precyzja", + "source": "Sensor wej\u015bciowy", + "time_window": "Okno czasowe", + "unit_prefix": "Prefiks metryczny", + "unit_time": "Jednostka czasu" + }, + "data_description": { + "round": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych.", + "time_window": "Je\u015bli jest ustawiona, warto\u015b\u0107 sensora jest wa\u017con\u0105 w czasie \u015bredni\u0105 ruchom\u0105 pochodnych w tym oknie.", + "unit_prefix": "Wynik b\u0119dzie skalowany zgodnie z wybranym prefiksem metrycznym i jednostk\u0105 czasu pochodnej." + } + }, + "options": { + "data": { + "name": "Nazwa", + "round": "Precyzja", + "source": "Sensor wej\u015bciowy", + "time_window": "Okno czasowe", + "unit_prefix": "Prefiks metryczny", + "unit_time": "Jednostka czasu" + }, + "data_description": { + "round": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych.", + "time_window": "Je\u015bli jest ustawiona, warto\u015b\u0107 sensora jest wa\u017con\u0105 w czasie \u015bredni\u0105 ruchom\u0105 pochodnych w tym oknie.", + "unit_prefix": "Wynik b\u0119dzie skalowany zgodnie z wybranym prefiksem metrycznym i jednostk\u0105 czasu pochodnej." + }, + "description": "Tworzy sensor, kt\u00f3ry szacuje pochodn\u0105 sensora." + } + } + }, + "title": "Sensor pochodnej" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/pt-BR.json b/homeassistant/components/derivative/translations/pt-BR.json new file mode 100644 index 00000000000..4d29a3970ee --- /dev/null +++ b/homeassistant/components/derivative/translations/pt-BR.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nome", + "round": "Precis\u00e3o", + "source": "Sensor de entrada", + "time_window": "Janela do tempo", + "unit_prefix": "Prefixo da m\u00e9trica", + "unit_time": "Unidade de tempo" + }, + "data_description": { + "round": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda.", + "time_window": "Se definido, o valor do sensor \u00e9 uma m\u00e9dia m\u00f3vel ponderada no tempo das derivadas dentro desta janela.", + "unit_prefix": "A sa\u00edda ser\u00e1 dimensionada de acordo com o prefixo m\u00e9trico selecionado e a unidade de tempo da derivada." + }, + "description": "Crie um sensor que estime a derivada de um sensor.", + "title": "Adicionar sensor Derivative" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nome", + "round": "Precis\u00e3o", + "source": "Sensor de entrada", + "time_window": "Janela do tempo", + "unit_prefix": "Prefixo da m\u00e9trica", + "unit_time": "Unidade de tempo" + }, + "data_description": { + "round": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda.", + "time_window": "Se definido, o valor do sensor \u00e9 uma m\u00e9dia m\u00f3vel ponderada no tempo das derivadas dentro desta janela.", + "unit_prefix": "A sa\u00edda ser\u00e1 dimensionada de acordo com o prefixo m\u00e9trico selecionado e a unidade de tempo da derivada.." + } + }, + "options": { + "data": { + "name": "Nome", + "round": "Precis\u00e3o", + "source": "Sensor de entrada", + "time_window": "Janela do tempo", + "unit_prefix": "Prefixo da m\u00e9trica", + "unit_time": "Unidade de tempo" + }, + "data_description": { + "round": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda.", + "time_window": "Se definido, o valor do sensor \u00e9 uma m\u00e9dia m\u00f3vel ponderada no tempo das derivadas dentro desta janela.", + "unit_prefix": "A sa\u00edda ser\u00e1 dimensionada de acordo com o prefixo m\u00e9trico selecionado e a unidade de tempo da derivada.." + }, + "description": "Crie um sensor que estime a derivada de um sensor." + } + } + }, + "title": "Sensor derivativo" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/ru.json b/homeassistant/components/derivative/translations/ru.json new file mode 100644 index 00000000000..d7c62f070e1 --- /dev/null +++ b/homeassistant/components/derivative/translations/ru.json @@ -0,0 +1,53 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "round": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "source": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "time_window": "\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u043e\u043a\u043d\u043e", + "unit_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0438", + "unit_time": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438" + }, + "data_description": { + "round": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439." + }, + "title": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u0430\u044f" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "round": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "source": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "time_window": "\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u043e\u043a\u043d\u043e", + "unit_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0438", + "unit_time": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438" + }, + "data_description": { + "round": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439.", + "unit_prefix": "." + } + }, + "options": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "round": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "source": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "time_window": "\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u043e\u043a\u043d\u043e", + "unit_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0438", + "unit_time": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438" + }, + "data_description": { + "round": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439.", + "unit_prefix": "." + } + } + } + }, + "title": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u0430\u044f" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/sv.json b/homeassistant/components/derivative/translations/sv.json new file mode 100644 index 00000000000..66dcd34b1d7 --- /dev/null +++ b/homeassistant/components/derivative/translations/sv.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "init": { + "data": { + "name": "Namn", + "round": "Precision" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/tr.json b/homeassistant/components/derivative/translations/tr.json new file mode 100644 index 00000000000..68016c74372 --- /dev/null +++ b/homeassistant/components/derivative/translations/tr.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Ad", + "round": "Hassas", + "source": "Giri\u015f sens\u00f6r\u00fc", + "time_window": "Zaman penceresi", + "unit_prefix": "Metrik \u00f6neki", + "unit_time": "Zaman birimi" + }, + "data_description": { + "round": "\u00c7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder.", + "time_window": "Ayarlan\u0131rsa, sens\u00f6r\u00fcn de\u011feri, bu penceredeki t\u00fcrevlerin zaman a\u011f\u0131rl\u0131kl\u0131 hareketli ortalamas\u0131d\u0131r.", + "unit_prefix": "\u00c7\u0131kt\u0131, t\u00fcrevin se\u00e7ilen metrik \u00f6nekine ve zaman birimine g\u00f6re \u00f6l\u00e7eklenecektir." + }, + "description": "Bir sens\u00f6r\u00fcn t\u00fcrevini tahmin eden bir sens\u00f6r olu\u015fturun.", + "title": "T\u00fcrev sens\u00f6r\u00fc ekle" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Ad", + "round": "Hassas", + "source": "Giri\u015f sens\u00f6r\u00fc", + "time_window": "Zaman penceresi", + "unit_prefix": "Metrik \u00f6neki", + "unit_time": "Zaman birimi" + }, + "data_description": { + "round": "\u00c7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder.", + "time_window": "Ayarlan\u0131rsa, sens\u00f6r\u00fcn de\u011feri, bu penceredeki t\u00fcrevlerin zaman a\u011f\u0131rl\u0131kl\u0131 hareketli ortalamas\u0131d\u0131r.", + "unit_prefix": "\u00c7\u0131kt\u0131, t\u00fcrevin se\u00e7ilen metrik \u00f6nekine ve zaman birimine g\u00f6re \u00f6l\u00e7eklenecektir.." + } + }, + "options": { + "data": { + "name": "Ad", + "round": "Hassas", + "source": "Giri\u015f sens\u00f6r\u00fc", + "time_window": "Zaman penceresi", + "unit_prefix": "Metrik \u00f6neki", + "unit_time": "Zaman birimi" + }, + "data_description": { + "round": "\u00c7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder.", + "time_window": "Ayarlan\u0131rsa, sens\u00f6r\u00fcn de\u011feri, bu penceredeki t\u00fcrevlerin zaman a\u011f\u0131rl\u0131kl\u0131 hareketli ortalamas\u0131d\u0131r.", + "unit_prefix": "\u00c7\u0131kt\u0131, t\u00fcrevin se\u00e7ilen metrik \u00f6nekine ve zaman birimine g\u00f6re \u00f6l\u00e7eklenecektir.." + }, + "description": "Bir sens\u00f6r\u00fcn t\u00fcrevini tahmin eden bir sens\u00f6r olu\u015fturun." + } + } + }, + "title": "T\u00fcrev sens\u00f6r\u00fc" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/zh-Hans.json b/homeassistant/components/derivative/translations/zh-Hans.json new file mode 100644 index 00000000000..689f057dec0 --- /dev/null +++ b/homeassistant/components/derivative/translations/zh-Hans.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u540d\u79f0", + "round": "\u7cbe\u5ea6", + "source": "\u8f93\u5165\u4f20\u611f\u5668", + "time_window": "\u65f6\u95f4\u7a97\u53e3", + "unit_prefix": "\u5355\u4f4d\u524d\u7f00", + "unit_time": "\u65f6\u95f4\u5355\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002", + "time_window": "\u5982\u679c\u8bbe\u7f6e\uff0c\u4f20\u611f\u5668\u5c06\u8f93\u51fa\u6b64\u65f6\u95f4\u7a97\u53e3\u5185\u7684\u53d8\u5316\u7387\u6309\u7167\u201c\u65f6\u95f4\u52a0\u6743\u79fb\u52a8\u5e73\u5747\u6cd5\u201d\u5904\u7406\u540e\u7684\u503c\u3002", + "unit_prefix": "\u8f93\u51fa\u503c\u5c06\u6839\u636e\u6240\u9009\u7684\u5355\u4f4d\u524d\u7f00\u548c\u65f6\u95f4\u5355\u4f4d\u8fdb\u884c\u7f29\u653e\u3002" + }, + "description": "\u521b\u5efa\u4f20\u611f\u5668\u6765\u4f30\u7b97\u53e6\u4e00\u4e2a\u4f20\u611f\u5668\u7684\u53d8\u5316\u7387\u3002", + "title": "\u6dfb\u52a0\u53d8\u5316\u7387\uff08\u5bfc\u6570\uff09\u4f20\u611f\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u540d\u79f0", + "round": "\u7cbe\u5ea6", + "source": "\u8f93\u5165\u4f20\u611f\u5668", + "time_window": "\u65f6\u95f4\u7a97\u53e3", + "unit_prefix": "\u5355\u4f4d\u524d\u7f00", + "unit_time": "\u65f6\u95f4\u5355\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002", + "time_window": "\u5982\u679c\u8bbe\u7f6e\uff0c\u4f20\u611f\u5668\u5c06\u8f93\u51fa\u6b64\u65f6\u95f4\u7a97\u53e3\u5185\u7684\u53d8\u5316\u7387\u6309\u7167\u201c\u65f6\u95f4\u52a0\u6743\u79fb\u52a8\u5e73\u5747\u6cd5\u201d\u5904\u7406\u540e\u7684\u503c\u3002", + "unit_prefix": "\u8f93\u51fa\u503c\u5c06\u6839\u636e\u6240\u9009\u7684\u5355\u4f4d\u524d\u7f00\u548c\u65f6\u95f4\u5355\u4f4d\u8fdb\u884c\u7f29\u653e\u3002" + } + }, + "options": { + "data": { + "name": "\u540d\u79f0", + "round": "\u7cbe\u5ea6", + "source": "\u8f93\u5165\u4f20\u611f\u5668", + "time_window": "\u65f6\u95f4\u7a97\u53e3", + "unit_prefix": "\u5355\u4f4d\u524d\u7f00", + "unit_time": "\u65f6\u95f4\u5355\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002", + "time_window": "\u5982\u679c\u8bbe\u7f6e\uff0c\u4f20\u611f\u5668\u5c06\u8f93\u51fa\u6b64\u65f6\u95f4\u7a97\u53e3\u5185\u7684\u53d8\u5316\u7387\u6309\u7167\u201c\u65f6\u95f4\u52a0\u6743\u79fb\u52a8\u5e73\u5747\u6cd5\u201d\u5904\u7406\u540e\u7684\u503c\u3002", + "unit_prefix": "\u8f93\u51fa\u503c\u5c06\u6839\u636e\u6240\u9009\u7684\u5355\u4f4d\u524d\u7f00\u548c\u65f6\u95f4\u5355\u4f4d\u8fdb\u884c\u7f29\u653e\u3002" + }, + "description": "\u521b\u5efa\u4f20\u611f\u5668\u6765\u4f30\u7b97\u53e6\u4e00\u4e2a\u4f20\u611f\u5668\u7684\u53d8\u5316\u7387\u3002" + } + } + }, + "title": "\u53d8\u5316\u7387\u4f20\u611f\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/derivative/translations/zh-Hant.json b/homeassistant/components/derivative/translations/zh-Hant.json new file mode 100644 index 00000000000..3c100df8034 --- /dev/null +++ b/homeassistant/components/derivative/translations/zh-Hant.json @@ -0,0 +1,59 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u540d\u7a31", + "round": "\u6e96\u78ba\u5ea6", + "source": "\u8f38\u5165\u611f\u6e2c\u5668", + "time_window": "\u6642\u9593\u8996\u7a97", + "unit_prefix": "\u516c\u5236\u524d\u7db4", + "unit_time": "\u6642\u9593\u55ae\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f38\u51fa\u4e2d\u7684\u5c0f\u6578\u4f4d\u6578\u3002", + "time_window": "\u8a2d\u5b9a\u5f8c\u3001\u611f\u6e2c\u5668\u6578\u503c\u5c07\u70ba\u8996\u7a97\u5167\u5c0e\u6578\u7684\u6642\u9593\u52a0\u6b0a\u52a0\u6b0a\u79fb\u52d5\u5e73\u5747\u503c\u3002", + "unit_prefix": "\u8f38\u51fa\u5c07\u53d7\u6240\u9078\u64c7\u516c\u5236\u524d\u7db4\u53ca\u5c0e\u6578\u6642\u9593\u55ae\u4f4d\u800c\u8b8a\u5316\u3002" + }, + "description": "\u65b0\u589e\u9810\u4f30\u611f\u6e2c\u5668\u5c0e\u6578\u4e4b\u611f\u6e2c\u5668\u3002", + "title": "\u65b0\u589e\u5c0e\u6578\u611f\u6e2c\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u540d\u7a31", + "round": "\u6e96\u78ba\u5ea6", + "source": "\u8f38\u5165\u611f\u6e2c\u5668", + "time_window": "\u6642\u9593\u8996\u7a97", + "unit_prefix": "\u516c\u5236\u524d\u7db4", + "unit_time": "\u6642\u9593\u55ae\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f38\u51fa\u4e2d\u7684\u5c0f\u6578\u4f4d\u6578\u3002", + "time_window": "\u8a2d\u5b9a\u5f8c\u3001\u611f\u6e2c\u5668\u6578\u503c\u5c07\u70ba\u8996\u7a97\u5167\u5c0e\u6578\u7684\u6642\u9593\u52a0\u6b0a\u52a0\u6b0a\u79fb\u52d5\u5e73\u5747\u503c\u3002", + "unit_prefix": "\u8f38\u51fa\u5c07\u53d7\u6240\u9078\u64c7\u516c\u5236\u524d\u7db4\u53ca\u5c0e\u6578\u6642\u9593\u55ae\u4f4d\u800c\u8b8a\u5316\u3002." + } + }, + "options": { + "data": { + "name": "\u540d\u7a31", + "round": "\u6e96\u78ba\u5ea6", + "source": "\u8f38\u5165\u611f\u6e2c\u5668", + "time_window": "\u6642\u9593\u8996\u7a97", + "unit_prefix": "\u516c\u5236\u524d\u7db4", + "unit_time": "\u6642\u9593\u55ae\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f38\u51fa\u4e2d\u7684\u5c0f\u6578\u4f4d\u6578\u3002", + "time_window": "\u8a2d\u5b9a\u5f8c\u3001\u611f\u6e2c\u5668\u6578\u503c\u5c07\u70ba\u8996\u7a97\u5167\u5c0e\u6578\u7684\u6642\u9593\u52a0\u6b0a\u52a0\u6b0a\u79fb\u52d5\u5e73\u5747\u503c\u3002", + "unit_prefix": "\u8f38\u51fa\u5c07\u53d7\u6240\u9078\u64c7\u516c\u5236\u524d\u7db4\u53ca\u5c0e\u6578\u6642\u9593\u55ae\u4f4d\u800c\u8b8a\u5316\u3002" + }, + "description": "\u65b0\u589e\u9810\u4f30\u611f\u6e2c\u5668\u5c0e\u6578\u4e4b\u611f\u6e2c\u5668\u3002" + } + } + }, + "title": "\u5c0e\u6578\u611f\u6e2c\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 46a3bf6815d..73f28c54657 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -13,8 +13,13 @@ import voluptuous as vol import voluptuous_serialize from homeassistant.components import websocket_api -from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM -from homeassistant.core import HomeAssistant +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_PLATFORM, +) +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -47,6 +52,7 @@ DEVICE_TRIGGER_BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( vol.Required(CONF_PLATFORM): "device", vol.Required(CONF_DOMAIN): str, vol.Required(CONF_DEVICE_ID): str, + vol.Remove("metadata"): dict, } ) @@ -166,6 +172,24 @@ async def async_get_device_automation_platform( return platform +@callback +def _async_set_entity_device_automation_metadata( + hass: HomeAssistant, automation: dict[str, Any] +) -> None: + """Set device automation metadata based on entity registry entry data.""" + if "metadata" not in automation: + automation["metadata"] = {} + if ATTR_ENTITY_ID not in automation or "secondary" in automation["metadata"]: + return + + entity_registry = er.async_get(hass) + # Guard against the entry being removed before this is called + if not (entry := entity_registry.async_get(automation[ATTR_ENTITY_ID])): + return + + automation["metadata"]["secondary"] = bool(entry.entity_category or entry.hidden_by) + + async def _async_get_device_automations_from_domain( hass, domain, automation_type, device_ids, return_exceptions ): @@ -242,6 +266,7 @@ async def async_get_device_automations( ) continue for automation in device_results: + _async_set_entity_device_automation_metadata(hass, automation) combined_results[automation["device_id"]].append(automation) return combined_results diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index 950d9257bae..cde7ca33152 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -8,11 +8,10 @@ from devolo_home_control_api.homecontrol import HomeControl from homeassistant.components.climate import ( ATTR_TEMPERATURE, - HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, TEMP_CELSIUS, ClimateEntity, ) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS from homeassistant.core import HomeAssistant @@ -60,12 +59,12 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit element_uid=element_uid, ) - self._attr_hvac_mode = HVAC_MODE_HEAT - self._attr_hvac_modes = [HVAC_MODE_HEAT] + self._attr_hvac_mode = HVACMode.HEAT + self._attr_hvac_modes = [HVACMode.HEAT] self._attr_min_temp = self._multi_level_switch_property.min self._attr_max_temp = self._multi_level_switch_property.max self._attr_precision = PRECISION_TENTHS - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE self._attr_target_temperature_step = PRECISION_HALVES self._attr_temperature_unit = TEMP_CELSIUS diff --git a/homeassistant/components/devolo_home_control/cover.py b/homeassistant/components/devolo_home_control/cover.py index bdb42969a28..a23c3fde585 100644 --- a/homeassistant/components/devolo_home_control/cover.py +++ b/homeassistant/components/devolo_home_control/cover.py @@ -7,11 +7,9 @@ from devolo_home_control_api.devices.zwave import Zwave from devolo_home_control_api.homecontrol import HomeControl from homeassistant.components.cover import ( - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -57,7 +55,9 @@ class DevoloCoverDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, CoverEntity): self._attr_device_class = CoverDeviceClass.BLIND self._attr_supported_features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION ) @property diff --git a/homeassistant/components/devolo_home_control/light.py b/homeassistant/components/devolo_home_control/light.py index ac8793e90b5..4ccd5a00ab2 100644 --- a/homeassistant/components/devolo_home_control/light.py +++ b/homeassistant/components/devolo_home_control/light.py @@ -6,11 +6,7 @@ from typing import Any from devolo_home_control_api.devices.zwave import Zwave from devolo_home_control_api.homecontrol import HomeControl -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -53,7 +49,8 @@ class DevoloLightDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, LightEntity): element_uid=element_uid, ) - self._attr_supported_features = SUPPORT_BRIGHTNESS + self._attr_color_mode = ColorMode.BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} self._binary_switch_property = device_instance.binary_switch_property.get( element_uid.replace("Dimmer", "BinarySwitch") ) diff --git a/homeassistant/components/devolo_home_control/siren.py b/homeassistant/components/devolo_home_control/siren.py index 36a3f82ca5f..216ab4ab296 100644 --- a/homeassistant/components/devolo_home_control/siren.py +++ b/homeassistant/components/devolo_home_control/siren.py @@ -4,13 +4,7 @@ from typing import Any from devolo_home_control_api.devices.zwave import Zwave from devolo_home_control_api.homecontrol import HomeControl -from homeassistant.components.siren import ( - ATTR_TONE, - SUPPORT_TONES, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SirenEntity, -) +from homeassistant.components.siren import ATTR_TONE, SirenEntity, SirenEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -43,6 +37,12 @@ async def async_setup_entry( class DevoloSirenDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, SirenEntity): """Representation of a cover device within devolo Home Control.""" + _attr_supported_features = ( + SirenEntityFeature.TURN_OFF + | SirenEntityFeature.TURN_ON + | SirenEntityFeature.TONES + ) + def __init__( self, homecontrol: HomeControl, device_instance: Zwave, element_uid: str ) -> None: @@ -58,9 +58,6 @@ class DevoloSirenDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, SirenEntity): int(self._multi_level_switch_property.max) + 1, ) ] - self._attr_supported_features = ( - SUPPORT_TURN_OFF | SUPPORT_TURN_ON | SUPPORT_TONES - ) self._default_tone = device_instance.settings_property["tone"].tone @property diff --git a/homeassistant/components/devolo_home_network/manifest.json b/homeassistant/components/devolo_home_network/manifest.json index a514606a322..445a383ea14 100644 --- a/homeassistant/components/devolo_home_network/manifest.json +++ b/homeassistant/components/devolo_home_network/manifest.json @@ -4,7 +4,9 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/devolo_home_network", "requirements": ["devolo-plc-api==0.7.1"], - "zeroconf": ["_dvl-deviceapi._tcp.local."], + "zeroconf": [ + { "type": "_dvl-deviceapi._tcp.local.", "properties": { "MT": "*" } } + ], "codeowners": ["@2Fake", "@Shutgun"], "quality_scale": "platinum", "iot_class": "local_polling", diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index a3f1e5fe272..29315d6c467 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -180,7 +180,7 @@ class DownloadDiagnosticsView(http.HomeAssistantView): extra_urls = ["/api/diagnostics/{d_type}/{d_id}/{sub_type}/{sub_id}"] name = "api:diagnostics" - async def get( # pylint: disable=no-self-use + async def get( self, request: web.Request, d_type: str, diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index 8db2399fd53..cbb8831e9b5 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -33,6 +33,10 @@ def async_redact_data(data: _T, to_redact: Iterable[Any]) -> _T: redacted = {**data} for key, value in redacted.items(): + if value is None: + continue + if isinstance(value, str) and not value: + continue if key in to_redact: redacted[key] = REDACTED elif isinstance(value, Mapping): diff --git a/homeassistant/components/digitalloggers/__init__.py b/homeassistant/components/digitalloggers/__init__.py deleted file mode 100644 index 6db88ca93d2..00000000000 --- a/homeassistant/components/digitalloggers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The digitalloggers component.""" diff --git a/homeassistant/components/digitalloggers/manifest.json b/homeassistant/components/digitalloggers/manifest.json deleted file mode 100644 index 51d5982a595..00000000000 --- a/homeassistant/components/digitalloggers/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain": "digitalloggers", - "name": "Digital Loggers", - "documentation": "https://www.home-assistant.io/integrations/digitalloggers", - "requirements": ["dlipower==0.7.165"], - "codeowners": [], - "iot_class": "local_polling", - "loggers": ["dlipower"] -} diff --git a/homeassistant/components/digitalloggers/switch.py b/homeassistant/components/digitalloggers/switch.py deleted file mode 100644 index 7ec0577d9db..00000000000 --- a/homeassistant/components/digitalloggers/switch.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Support for Digital Loggers DIN III Relays.""" -from __future__ import annotations - -from datetime import timedelta -import logging - -import dlipower -import voluptuous as vol - -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity -from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_USERNAME, -) -from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.util import Throttle - -_LOGGER = logging.getLogger(__name__) - -CONF_CYCLETIME = "cycletime" - -DEFAULT_NAME = "DINRelay" -DEFAULT_USERNAME = "admin" -DEFAULT_PASSWORD = "admin" -DEFAULT_TIMEOUT = 20 -DEFAULT_CYCLETIME = 2 - -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.All( - vol.Coerce(int), vol.Range(min=1, max=600) - ), - vol.Optional(CONF_CYCLETIME, default=DEFAULT_CYCLETIME): vol.All( - vol.Coerce(int), vol.Range(min=1, max=600) - ), - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Find and return DIN III Relay switch.""" - - host = config[CONF_HOST] - controller_name = config[CONF_NAME] - user = config[CONF_USERNAME] - pswd = config[CONF_PASSWORD] - tout = config[CONF_TIMEOUT] - cycl = config[CONF_CYCLETIME] - - power_switch = dlipower.PowerSwitch( - hostname=host, userid=user, password=pswd, timeout=tout, cycletime=cycl - ) - - if not power_switch.verify(): - _LOGGER.error("Could not connect to DIN III Relay") - return - - entities: list[DINRelay] = [] - parent_device = DINRelayDevice(power_switch) - - entities.extend( - DINRelay(controller_name, parent_device, outlet) for outlet in power_switch[0:] - ) - - add_entities(entities) - - -class DINRelay(SwitchEntity): - """Representation of an individual DIN III relay port.""" - - def __init__(self, controller_name, parent_device, outlet): - """Initialize the DIN III Relay switch.""" - self._controller_name = controller_name - self._parent_device = parent_device - self._outlet = outlet - - self._outlet_number = self._outlet.outlet_number - self._name = self._outlet.description - self._state = self._outlet.state == "ON" - - @property - def name(self): - """Return the display name of this relay.""" - return f"{self._controller_name}_{self._name}" - - @property - def is_on(self): - """Return true if relay is on.""" - return self._state - - def turn_on(self, **kwargs): - """Instruct the relay to turn on.""" - self._outlet.on() - - def turn_off(self, **kwargs): - """Instruct the relay to turn off.""" - self._outlet.off() - - def update(self): - """Trigger update for all switches on the parent device.""" - self._parent_device.update() - - outlet_status = self._parent_device.get_outlet_status(self._outlet_number) - - self._name = outlet_status[1] - self._state = outlet_status[2] == "ON" - - -class DINRelayDevice: - """Device representation for per device throttling.""" - - def __init__(self, power_switch): - """Initialize the DINRelay device.""" - self._power_switch = power_switch - self._statuslist = None - - def get_outlet_status(self, outlet_number): - """Get status of outlet from cached status list.""" - return self._statuslist[outlet_number - 1] - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Fetch new state data for this device.""" - self._statuslist = self._power_switch.statuslist() diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index b965eb4e7ea..2fb5acf8295 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -8,20 +8,13 @@ from directv import DIRECTV from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING @@ -43,23 +36,23 @@ _LOGGER = logging.getLogger(__name__) KNOWN_MEDIA_TYPES = [MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW] SUPPORT_DTV = ( - SUPPORT_PAUSE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_PLAY + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.PLAY ) SUPPORT_DTV_CLIENT = ( - SUPPORT_PAUSE - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_PLAY + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.PLAY ) diff --git a/homeassistant/components/directv/translations/it.json b/homeassistant/components/directv/translations/it.json index 9fb0932c342..3653e3876c2 100644 --- a/homeassistant/components/directv/translations/it.json +++ b/homeassistant/components/directv/translations/it.json @@ -11,8 +11,8 @@ "step": { "ssdp_confirm": { "data": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "description": "Vuoi impostare {name} ?" }, diff --git a/homeassistant/components/discord/translations/bg.json b/homeassistant/components/discord/translations/bg.json new file mode 100644 index 00000000000..00faba1155f --- /dev/null +++ b/homeassistant/components/discord/translations/bg.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/ca.json b/homeassistant/components/discord/translations/ca.json new file mode 100644 index 00000000000..bd87ba1f8dc --- /dev/null +++ b/homeassistant/components/discord/translations/ca.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "El servei ja est\u00e0 configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token d'API" + }, + "description": "Consulta la documentaci\u00f3 per obtenir la teva clau de bot de Discord. \n\n{url}" + }, + "user": { + "data": { + "api_token": "Token d'API" + }, + "description": "Consulta la documentaci\u00f3 per obtenir la teva clau de bot de Discord. \n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/de.json b/homeassistant/components/discord/translations/de.json new file mode 100644 index 00000000000..e4f745573c0 --- /dev/null +++ b/homeassistant/components/discord/translations/de.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API-Token" + }, + "description": "Weitere Informationen zum Abrufen deines Discord-Bot-Schl\u00fcssels findest du in der Dokumentation. \n\n {url}" + }, + "user": { + "data": { + "api_token": "API-Token" + }, + "description": "Weitere Informationen zum Abrufen deines Discord-Bot-Schl\u00fcssels findest du in der Dokumentation. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/el.json b/homeassistant/components/discord/translations/el.json new file mode 100644 index 00000000000..a090d61d3c4 --- /dev/null +++ b/homeassistant/components/discord/translations/el.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" + }, + "description": "\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd Discord bot. \n\n {url}" + }, + "user": { + "data": { + "api_token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc API" + }, + "description": "\u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd Discord bot. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/en.json b/homeassistant/components/discord/translations/en.json index 77e16e9312d..ee72905b4d3 100644 --- a/homeassistant/components/discord/translations/en.json +++ b/homeassistant/components/discord/translations/en.json @@ -10,13 +10,13 @@ "unknown": "Unexpected error" }, "step": { - "user": { + "reauth_confirm": { "data": { "api_token": "API Token" }, "description": "Refer to the documentation on getting your Discord bot key.\n\n{url}" }, - "reauth_confirm": { + "user": { "data": { "api_token": "API Token" }, diff --git a/homeassistant/components/discord/translations/et.json b/homeassistant/components/discord/translations/et.json new file mode 100644 index 00000000000..ba69a715841 --- /dev/null +++ b/homeassistant/components/discord/translations/et.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Teenus on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API v\u00f5ti" + }, + "description": "Vaata oma Discordi roboti v\u00f5tme hankimise dokumentatsiooni. \n\n {url}" + }, + "user": { + "data": { + "api_token": "API v\u00f5ti" + }, + "description": "Vaata oma Discordi roboti v\u00f5tme hankimise dokumentatsiooni. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/fr.json b/homeassistant/components/discord/translations/fr.json new file mode 100644 index 00000000000..3ffac86c505 --- /dev/null +++ b/homeassistant/components/discord/translations/fr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Jeton d'API" + }, + "description": "Consultez la documentation pour obtenir votre cl\u00e9 de bot Discord.\n\n{url}" + }, + "user": { + "data": { + "api_token": "Jeton d'API" + }, + "description": "Consultez la documentation pour obtenir votre cl\u00e9 de bot Discord.\n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/he.json b/homeassistant/components/discord/translations/he.json new file mode 100644 index 00000000000..d23c46a99bc --- /dev/null +++ b/homeassistant/components/discord/translations/he.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API" + } + }, + "user": { + "data": { + "api_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/hu.json b/homeassistant/components/discord/translations/hu.json new file mode 100644 index 00000000000..bcbd8748808 --- /dev/null +++ b/homeassistant/components/discord/translations/hu.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API Token" + }, + "description": "Tekintse meg a Discord botkulcs beszerz\u00e9s\u00e9nek dokument\u00e1ci\u00f3j\u00e1t. \n\n {url}" + }, + "user": { + "data": { + "api_token": "API Token" + }, + "description": "Tekintse meg a Discord botkulcs beszerz\u00e9s\u00e9nek dokument\u00e1ci\u00f3j\u00e1t. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/id.json b/homeassistant/components/discord/translations/id.json new file mode 100644 index 00000000000..2e5d7d41f23 --- /dev/null +++ b/homeassistant/components/discord/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token API" + }, + "description": "Lihat dokumentasi tentang mendapatkan kunci bot Discord Anda. \n\n {url}" + }, + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Lihat dokumentasi tentang mendapatkan kunci bot Discord Anda. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/it.json b/homeassistant/components/discord/translations/it.json new file mode 100644 index 00000000000..c8be2cb0ed1 --- /dev/null +++ b/homeassistant/components/discord/translations/it.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token API" + }, + "description": "Fai riferimento alla documentazione su come ottenere la chiave del bot Discord. \n\n {url}" + }, + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Fai riferimento alla documentazione su come ottenere la chiave del bot Discord. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/ja.json b/homeassistant/components/discord/translations/ja.json new file mode 100644 index 00000000000..3d96ea99890 --- /dev/null +++ b/homeassistant/components/discord/translations/ja.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API\u30c8\u30fc\u30af\u30f3" + }, + "description": "Discord\u30dc\u30c3\u30c8\u30ad\u30fc\u306e\u53d6\u5f97\u306b\u3064\u3044\u3066\u306f\u3001\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n{url}" + }, + "user": { + "data": { + "api_token": "API\u30c8\u30fc\u30af\u30f3" + }, + "description": "Discord\u30dc\u30c3\u30c8\u30ad\u30fc\u306e\u53d6\u5f97\u306b\u3064\u3044\u3066\u306f\u3001\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/nl.json b/homeassistant/components/discord/translations/nl.json new file mode 100644 index 00000000000..55fb894031d --- /dev/null +++ b/homeassistant/components/discord/translations/nl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Service is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API-token" + }, + "description": "Raadpleeg de documentatie over het verkrijgen van uw Discord bot key.\n\n{url}" + }, + "user": { + "data": { + "api_token": "API-token" + }, + "description": "Raadpleeg de documentatie over het verkrijgen van uw Discord bot key.\n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/no.json b/homeassistant/components/discord/translations/no.json new file mode 100644 index 00000000000..e8a36e5c794 --- /dev/null +++ b/homeassistant/components/discord/translations/no.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API-token" + }, + "description": "Se dokumentasjonen for \u00e5 f\u00e5 din Discord-botn\u00f8kkel. \n\n {url}" + }, + "user": { + "data": { + "api_token": "API-token" + }, + "description": "Se dokumentasjonen for \u00e5 f\u00e5 din Discord-botn\u00f8kkel. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/pl.json b/homeassistant/components/discord/translations/pl.json new file mode 100644 index 00000000000..96fb45d1bc5 --- /dev/null +++ b/homeassistant/components/discord/translations/pl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token API" + }, + "description": "Zapoznaj si\u0119 z dokumentacj\u0105 dotycz\u0105c\u0105 uzyskiwania klucza bota Discord. \n\n{url}" + }, + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Zapoznaj si\u0119 z dokumentacj\u0105 dotycz\u0105c\u0105 uzyskiwania klucza bota Discord. \n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/pt-BR.json b/homeassistant/components/discord/translations/pt-BR.json new file mode 100644 index 00000000000..cedd60f6fb6 --- /dev/null +++ b/homeassistant/components/discord/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "Token da API" + }, + "description": "Consulte a documenta\u00e7\u00e3o sobre como obter sua chave de bot do Discord. \n\n {url}" + }, + "user": { + "data": { + "api_token": "Token da API" + }, + "description": "Consulte a documenta\u00e7\u00e3o sobre como obter sua chave de bot do Discord. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/ru.json b/homeassistant/components/discord/translations/ru.json new file mode 100644 index 00000000000..19be65ea874 --- /dev/null +++ b/homeassistant/components/discord/translations/ru.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439 \u043f\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044e \u043a\u043b\u044e\u0447\u0430 \u0431\u043e\u0442\u0430 Discord. \n\n{url}" + }, + "user": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439 \u043f\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044e \u043a\u043b\u044e\u0447\u0430 \u0431\u043e\u0442\u0430 Discord. \n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/tr.json b/homeassistant/components/discord/translations/tr.json new file mode 100644 index 00000000000..2bce7e54907 --- /dev/null +++ b/homeassistant/components/discord/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API Anahtar\u0131" + }, + "description": "Discord bot anahtar\u0131n\u0131z\u0131 almayla ilgili belgelere bak\u0131n. \n\n {url}" + }, + "user": { + "data": { + "api_token": "API Anahtar\u0131" + }, + "description": "Discord bot anahtar\u0131n\u0131z\u0131 almayla ilgili belgelere bak\u0131n. \n\n {url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discord/translations/zh-Hant.json b/homeassistant/components/discord/translations/zh-Hant.json new file mode 100644 index 00000000000..5f0f4500903 --- /dev/null +++ b/homeassistant/components/discord/translations/zh-Hant.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "reauth_confirm": { + "data": { + "api_token": "API \u6b0a\u6756" + }, + "description": "\u8acb\u53c3\u8003\u6587\u4ef6\u4ee5\u53d6\u5f97 Discord \u6a5f\u5668\u4eba\u91d1\u9470\u3002\n\n{url}" + }, + "user": { + "data": { + "api_token": "API \u6b0a\u6756" + }, + "description": "\u8acb\u53c3\u8003\u6587\u4ef6\u4ee5\u53d6\u5f97 Discord \u6a5f\u5668\u4eba\u91d1\u9470\u3002\n\n{url}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index fb7ac2b270d..a0ffbf235ab 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -58,7 +58,6 @@ class ServiceDetails(NamedTuple): # These have no config flows SERVICE_HANDLERS = { SERVICE_ENIGMA2: ServiceDetails("media_player", "enigma2"), - SERVICE_SABNZBD: ServiceDetails("sabnzbd", None), "yamaha": ServiceDetails("media_player", "yamaha"), "frontier_silicon": ServiceDetails("media_player", "frontier_silicon"), "openhome": ServiceDetails("media_player", "openhome"), @@ -97,6 +96,7 @@ MIGRATED_SERVICE_HANDLERS = [ SERVICE_XIAOMI_GW, "volumio", SERVICE_YEELIGHT, + SERVICE_SABNZBD, "nanoleaf_aurora", ] diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index ccd0ca6e922..e8cdb282118 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Renderer", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", - "requirements": ["async-upnp-client==0.27.0"], + "requirements": ["async-upnp-client==0.29.0"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index d4d994a779b..101b59c7125 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -21,6 +21,7 @@ from homeassistant.components import media_source, ssdp from homeassistant.components.media_player import ( BrowseMedia, MediaPlayerEntity, + MediaPlayerEntityFeature, async_process_play_media_url, ) from homeassistant.components.media_player.const import ( @@ -28,19 +29,6 @@ from homeassistant.components.media_player.const import ( REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, ) from homeassistant.const import ( CONF_DEVICE_ID, @@ -503,32 +491,35 @@ class DlnaDmrEntity(MediaPlayerEntity): supported_features = 0 if self._device.has_volume_level: - supported_features |= SUPPORT_VOLUME_SET + supported_features |= MediaPlayerEntityFeature.VOLUME_SET if self._device.has_volume_mute: - supported_features |= SUPPORT_VOLUME_MUTE + supported_features |= MediaPlayerEntityFeature.VOLUME_MUTE if self._device.can_play: - supported_features |= SUPPORT_PLAY + supported_features |= MediaPlayerEntityFeature.PLAY if self._device.can_pause: - supported_features |= SUPPORT_PAUSE + supported_features |= MediaPlayerEntityFeature.PAUSE if self._device.can_stop: - supported_features |= SUPPORT_STOP + supported_features |= MediaPlayerEntityFeature.STOP if self._device.can_previous: - supported_features |= SUPPORT_PREVIOUS_TRACK + supported_features |= MediaPlayerEntityFeature.PREVIOUS_TRACK if self._device.can_next: - supported_features |= SUPPORT_NEXT_TRACK + supported_features |= MediaPlayerEntityFeature.NEXT_TRACK if self._device.has_play_media: - supported_features |= SUPPORT_PLAY_MEDIA | SUPPORT_BROWSE_MEDIA + supported_features |= ( + MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) if self._device.can_seek_rel_time: - supported_features |= SUPPORT_SEEK + supported_features |= MediaPlayerEntityFeature.SEEK play_modes = self._device.valid_play_modes if play_modes & {PlayMode.RANDOM, PlayMode.SHUFFLE}: - supported_features |= SUPPORT_SHUFFLE_SET + supported_features |= MediaPlayerEntityFeature.SHUFFLE_SET if play_modes & {PlayMode.REPEAT_ONE, PlayMode.REPEAT_ALL}: - supported_features |= SUPPORT_REPEAT_SET + supported_features |= MediaPlayerEntityFeature.REPEAT_SET if self._device.has_presets: - supported_features |= SUPPORT_SELECT_SOUND_MODE + supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE return supported_features diff --git a/homeassistant/components/dlna_dmr/translations/hu.json b/homeassistant/components/dlna_dmr/translations/hu.json index 6596e71e05e..5e2ba148602 100644 --- a/homeassistant/components/dlna_dmr/translations/hu.json +++ b/homeassistant/components/dlna_dmr/translations/hu.json @@ -21,7 +21,7 @@ "description": "Kezd\u0151dhet a be\u00e1ll\u00edt\u00e1s?" }, "import_turn_on": { - "description": "Kapcsolja be az eszk\u00f6zt, \u00e9s kattintson a K\u00fcld\u00e9s gombra a migr\u00e1ci\u00f3 folytat\u00e1s\u00e1hoz" + "description": "Kapcsolja be az eszk\u00f6zt, majd folytassa a migr\u00e1ci\u00f3t" }, "manual": { "data": { @@ -35,7 +35,7 @@ "host": "C\u00edm", "url": "URL" }, - "description": "V\u00e1lassz egy be\u00e1ll\u00edtand\u00f3 eszk\u00f6zt vagy adj meg egy URL-t", + "description": "V\u00e1lassza ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6zt, vagy hagyja \u00fcresen az URL-c\u00edm k\u00e9zi megad\u00e1s\u00e1hoz", "title": "DLNA digit\u00e1lis m\u00e9dia renderel\u0151" } } diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index dffd0b9b654..20c38a5be3f 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dms", - "requirements": ["async-upnp-client==0.27.0"], + "requirements": ["async-upnp-client==0.29.0"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/dlna_dms/translations/cs.json b/homeassistant/components/dlna_dms/translations/cs.json new file mode 100644 index 00000000000..cfcad870880 --- /dev/null +++ b/homeassistant/components/dlna_dms/translations/cs.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" + }, + "flow_title": "{name}", + "step": { + "confirm": { + "description": "Chcete za\u010d\u00edt nastavovat?" + }, + "user": { + "data": { + "host": "Hostitel" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dlna_dms/translations/hu.json b/homeassistant/components/dlna_dms/translations/hu.json index 8c645d42aa8..74ba47053a4 100644 --- a/homeassistant/components/dlna_dms/translations/hu.json +++ b/homeassistant/components/dlna_dms/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "bad_ssdp": "Az SSDP-adatok hi\u00e1nyosak", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", "not_dms": "A m\u00e9diaszerver eszk\u00f6z nem t\u00e1mogatott" diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index a8be4e4fcdb..2ea550c3792 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,7 +2,7 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==9.0.1"], + "requirements": ["pydoods==1.0.2", "pillow==9.1.0"], "codeowners": [], "iot_class": "local_polling", "loggers": ["pydoods"] diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 502ff453a27..8a2c06c7157 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -336,7 +336,6 @@ class DoorBirdRequestView(HomeAssistantView): async def get(self, request, event): """Respond to requests from the device.""" - # pylint: disable=no-self-use hass = request.app["hass"] token = request.query.get("token") diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index eb143b0f2dc..77dc8a2d45c 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -8,7 +8,7 @@ import logging import aiohttp import async_timeout -from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -96,7 +96,9 @@ class DoorBirdCamera(DoorBirdEntity, Camera): self._stream_url = stream_url self._attr_name = name self._last_image: bytes | None = None - self._attr_supported_features = SUPPORT_STREAM if self._stream_url else 0 + self._attr_supported_features = ( + CameraEntityFeature.STREAM if self._stream_url else 0 + ) self._interval = interval self._last_update = datetime.datetime.min self._attr_unique_id = f"{self._mac_addr}_{camera_id}" diff --git a/homeassistant/components/doorbird/translations/ca.json b/homeassistant/components/doorbird/translations/ca.json index 880360234b1..0049709caa3 100644 --- a/homeassistant/components/doorbird/translations/ca.json +++ b/homeassistant/components/doorbird/translations/ca.json @@ -29,6 +29,9 @@ "data": { "events": "Llista d'esdeveniments separats per comes." }, + "data_description": { + "events": "Afegeix el/s noms del/s esdeveniment/s que vulguis seguir separats per comes. Despr\u00e9s d'introduir-los, utilitza l'aplicaci\u00f3 de DoorBird per assignar-los a un esdeveniment espec\u00edfic.\n\nExemple: algu_ha_premut_el_boto, moviment" + }, "description": "Afegeix el/s noms del/s esdeveniment/s que vulguis seguir separats per comes. Despr\u00e9s d'introduir-los, utilitza l'aplicaci\u00f3 de DoorBird per assignar-los a un esdeveniment espec\u00edfic. Consulta la documentaci\u00f3 a https://www.home-assistant.io/integrations/doorbird/#events.\nExemple: algu_ha_premut_el_boto, moviment_detectat" } } diff --git a/homeassistant/components/doorbird/translations/de.json b/homeassistant/components/doorbird/translations/de.json index 3f025e67386..7a60a3bf4ae 100644 --- a/homeassistant/components/doorbird/translations/de.json +++ b/homeassistant/components/doorbird/translations/de.json @@ -29,6 +29,9 @@ "data": { "events": "Durch Kommas getrennte Liste von Ereignissen." }, + "data_description": { + "events": "F\u00fcge f\u00fcr jedes Ereignis, das du verfolgen m\u00f6chtest, einen durch Komma getrennten Ereignisnamen hinzu. Nachdem du sie hier eingegeben hast, verwende die DoorBird-App, um sie einem bestimmten Ereignis zuzuordnen.\n\nBeispiel: jemand_drueckte_den_Knopf, bewegung" + }, "description": "F\u00fcge f\u00fcr jedes Ereignis, das du verfolgen m\u00f6chtest, einen durch Kommas getrennten Ereignisnamen hinzu. Nachdem du sie hier eingegeben hast, verwende die DoorBird-App, um sie einem bestimmten Ereignis zuzuweisen. Weitere Informationen findest du in der Dokumentation unter https://www.home-assistant.io/integrations/doorbird/#events. Beispiel: jemand_hat_den_knopf_gedr\u00fcckt, bewegung" } } diff --git a/homeassistant/components/doorbird/translations/el.json b/homeassistant/components/doorbird/translations/el.json index ffbf523e7d8..da06dc3e0be 100644 --- a/homeassistant/components/doorbird/translations/el.json +++ b/homeassistant/components/doorbird/translations/el.json @@ -29,6 +29,9 @@ "data": { "events": "\u039b\u03af\u03c3\u03c4\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1." }, + "data_description": { + "events": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03b5\u03c4\u03b5. \u0391\u03c6\u03bf\u03cd \u03c4\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03b4\u03ce, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae DoorBird \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03b9\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c3\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03bf \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd.\n\n\u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: somebody_pressed_the_button, motion" + }, "description": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03b5\u03c4\u03b5. \u0391\u03c6\u03bf\u03cd \u03c4\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03b5\u03b4\u03ce, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae DoorBird \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03b1 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03b9\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c3\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03bf \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 https://www.home-assistant.io/integrations/doorbird/#events. \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: somebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/doorbird/translations/en.json b/homeassistant/components/doorbird/translations/en.json index db1cea2d73f..ee005dfbfed 100644 --- a/homeassistant/components/doorbird/translations/en.json +++ b/homeassistant/components/doorbird/translations/en.json @@ -29,6 +29,9 @@ "data": { "events": "Comma separated list of events." }, + "data_description": { + "events": "Add an comma separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion" + }, "description": "Add an comma separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event. See the documentation at https://www.home-assistant.io/integrations/doorbird/#events. Example: somebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/doorbird/translations/et.json b/homeassistant/components/doorbird/translations/et.json index 08b163c0ca2..f37d5d0244b 100644 --- a/homeassistant/components/doorbird/translations/et.json +++ b/homeassistant/components/doorbird/translations/et.json @@ -29,6 +29,9 @@ "data": { "events": "Komadega eraldatud s\u00fcndmuste loend." }, + "data_description": { + "events": "Lisa iga s\u00fcndmuse jaoks, mida soovid j\u00e4lgida, komaga eraldatud s\u00fcndmuse nimi. P\u00e4rast nende sisestamist siia kasuta DoorBirdi rakendust, et m\u00e4\u00e4rata need konkreetsele s\u00fcndmusele.\n\nN\u00e4ide: somebody_pressed_the_button, liikumine" + }, "description": "Lisa komaga eraldatud s\u00fcndmuse nimi igale j\u00e4lgitavale s\u00fcndmusele. P\u00e4rast nende sisestamist kasuta rakendust DoorBird, et siduda need konkreetse s\u00fcndmusega. Vaata dokumentatsiooni aadressil https://www.home-assistant.io/integrations/doorbird/#events. N\u00e4ide: somebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/doorbird/translations/fr.json b/homeassistant/components/doorbird/translations/fr.json index 40569250e5f..b4819897366 100644 --- a/homeassistant/components/doorbird/translations/fr.json +++ b/homeassistant/components/doorbird/translations/fr.json @@ -29,6 +29,9 @@ "data": { "events": "Liste d'\u00e9v\u00e9nements s\u00e9par\u00e9s par des virgules." }, + "data_description": { + "events": "Ajoutez les noms des \u00e9v\u00e9nements que vous souhaitez suivre, s\u00e9par\u00e9s par des virgules. Apr\u00e8s les avoir saisis ici, utilisez l'application DoorBird pour les affecter \u00e0 un \u00e9v\u00e9nement sp\u00e9cifique.\n\nExemple\u00a0: somebody_pressed_the_button, motion" + }, "description": "Ajoutez un nom d'\u00e9v\u00e9nement s\u00e9par\u00e9 par des virgules pour chaque \u00e9v\u00e9nement que vous souhaitez suivre. Apr\u00e8s les avoir saisis ici, utilisez l'application DoorBird pour les affecter \u00e0 un \u00e9v\u00e9nement sp\u00e9cifique. Consultez la documentation sur https://www.home-assistant.io/integrations/doorbird/#events. Exemple: somebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/doorbird/translations/hu.json b/homeassistant/components/doorbird/translations/hu.json index 48a124b4f17..51bbb0864dd 100644 --- a/homeassistant/components/doorbird/translations/hu.json +++ b/homeassistant/components/doorbird/translations/hu.json @@ -29,6 +29,9 @@ "data": { "events": "Vessz\u0151vel elv\u00e1lasztott esem\u00e9nyek list\u00e1ja." }, + "data_description": { + "events": "Adjon hozz\u00e1 egy vessz\u0151vel elv\u00e1lasztott esem\u00e9nynevet minden egyes esem\u00e9nyhez, amelyet nyomon k\u00edv\u00e1n k\u00f6vetni. Miut\u00e1n be\u00edrta \u0151ket ide, a DoorBird alkalmaz\u00e1ssal rendelje \u0151ket egy adott esem\u00e9nyhez.\n\nP\u00e9lda: valaki_megnyomta_a_gombot, motion" + }, "description": "Adjon hozz\u00e1 vessz\u0151vel elv\u00e1lasztott esem\u00e9nynevet minden k\u00f6vetni k\u00edv\u00e1nt esem\u00e9nyhez. Miut\u00e1n itt megadta \u0151ket, haszn\u00e1lja a DoorBird alkalmaz\u00e1st, hogy hozz\u00e1rendelje \u0151ket egy adott esem\u00e9nyhez. Tekintse meg a dokument\u00e1ci\u00f3t a https://www.home-assistant.io/integrations/doorbird/#events c\u00edmen. P\u00e9lda: valaki_pr\u00e9selt_gomb, mozg\u00e1s" } } diff --git a/homeassistant/components/doorbird/translations/id.json b/homeassistant/components/doorbird/translations/id.json index 60348ec26a1..c5d5733457d 100644 --- a/homeassistant/components/doorbird/translations/id.json +++ b/homeassistant/components/doorbird/translations/id.json @@ -29,6 +29,9 @@ "data": { "events": "Daftar event yang dipisahkan koma." }, + "data_description": { + "events": "Tambahkan nama event yang dipisahkan koma untuk setiap event yang ingin dilacak. Setelah memasukkannya di sini, gunakan aplikasi DoorBird untuk menetapkannya ke event tertentu.\n\nMisalnya: ada_yang_menekan_tombol, gerakan" + }, "description": "Tambahkan nama event yang dipisahkan koma untuk setiap event yang ingin dilacak. Setelah memasukkannya di sini, gunakan aplikasi DoorBird untuk menetapkannya ke event tertentu. Baca dokumentasi di https://www.home-assistant.io/integrations/doorbird/#events. Contoh: somebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/doorbird/translations/it.json b/homeassistant/components/doorbird/translations/it.json index 83f1ab9c5eb..9864f100954 100644 --- a/homeassistant/components/doorbird/translations/it.json +++ b/homeassistant/components/doorbird/translations/it.json @@ -29,6 +29,9 @@ "data": { "events": "Elenco di eventi separati da virgole." }, + "data_description": { + "events": "Aggiungi un nome evento separato da virgole per ogni evento che desideri monitorare. Dopo averli inseriti qui, usa l'applicazione DoorBird per assegnarli a un evento specifico. \n\nEsempio: somebody_pressed_the_button, movimento" + }, "description": "Aggiungere un nome di evento separato da virgola per ogni evento che desideri monitorare. Dopo averli inseriti qui, usa l'applicazione DoorBird per assegnarli a un evento specifico. Consulta la documentazione su https://www.home-assistant.io/integrations/doorbird/#events. Esempio: qualcuno_premuto_il_pulsante, movimento" } } diff --git a/homeassistant/components/doorbird/translations/ja.json b/homeassistant/components/doorbird/translations/ja.json index 179edc8943c..55363310ec5 100644 --- a/homeassistant/components/doorbird/translations/ja.json +++ b/homeassistant/components/doorbird/translations/ja.json @@ -29,6 +29,9 @@ "data": { "events": "\u30a4\u30d9\u30f3\u30c8\u306e\u30b3\u30f3\u30de\u533a\u5207\u308a\u30ea\u30b9\u30c8\u3002" }, + "data_description": { + "events": "\u8ffd\u8de1\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u3054\u3068\u306b\u30b3\u30f3\u30de\u533a\u5207\u308a\u306e\u30a4\u30d9\u30f3\u30c8\u540d\u3092\u8ffd\u52a0\u3057\u307e\u3059\u3002\u3053\u3053\u306b\u5165\u529b\u5f8c\u3001DoorBird\u30a2\u30d7\u30ea\u3092\u4f7f\u7528\u3057\u3066\u7279\u5b9a\u306e\u30a4\u30d9\u30f3\u30c8\u306b\u5272\u308a\u5f53\u3066\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002 \n\n\u4f8b: somebody_pressed_the_button, motion" + }, "description": "\u8ffd\u8de1\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u3054\u3068\u306b\u3001\u30b3\u30f3\u30de\u533a\u5207\u308a\u3067\u30a4\u30d9\u30f3\u30c8\u540d\u3092\u8ffd\u52a0\u3057\u307e\u3059\u3002\u3053\u3053\u306b\u5165\u529b\u3057\u305f\u5f8c\u3001DoorBird\u30a2\u30d7\u30ea\u3092\u4f7f\u7528\u3057\u3066\u7279\u5b9a\u306e\u30a4\u30d9\u30f3\u30c8\u306b\u5272\u308a\u5f53\u3066\u307e\u3059\u3002https://www.home-assistant.io/integrations/doorbird/#events. \u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u4f8b: somebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json index db32d96d831..83e47ca4c5e 100644 --- a/homeassistant/components/doorbird/translations/nl.json +++ b/homeassistant/components/doorbird/translations/nl.json @@ -29,6 +29,9 @@ "data": { "events": "Door komma's gescheiden lijst met gebeurtenissen." }, + "data_description": { + "events": "Voeg een door komma's gescheiden evenementnaam toe voor elk evenement dat u wilt volgen. Nadat u ze hier hebt ingevoerd, gebruikt u de DoorBird app om ze aan een specifieke gebeurtenis toe te wijzen.\n\nVoorbeeld: iemand_drukt_op_de_knop, beweging" + }, "description": "Voeg een door komma's gescheiden evenementnaam toe voor elk evenement dat u wilt volgen. Nadat je ze hier hebt ingevoerd, gebruik je de DoorBird-app om ze toe te wijzen aan een specifiek evenement. Zie de documentatie op https://www.home-assistant.io/integrations/doorbird/#events. Voorbeeld: iemand_drukte_knop, beweging" } } diff --git a/homeassistant/components/doorbird/translations/no.json b/homeassistant/components/doorbird/translations/no.json index 356c86a8b54..a30fc93013a 100644 --- a/homeassistant/components/doorbird/translations/no.json +++ b/homeassistant/components/doorbird/translations/no.json @@ -29,6 +29,9 @@ "data": { "events": "Kommaseparert liste over hendelser." }, + "data_description": { + "events": "Legg til et navn p\u00e5 en kommadelt hendelse for hver hendelse du vil spore. N\u00e5r du har skrevet dem inn her, bruker du DoorBird-appen til \u00e5 tilordne dem til en bestemt hendelse.\n\nEksempel: somebody_pressed_the_button, bevegelse" + }, "description": "Legg til et kommaseparert hendelsesnavn for hvert arrangement du \u00f8nsker \u00e5 spore. Etter \u00e5 ha skrevet dem inn her, bruker du DoorBird-appen til \u00e5 tilordne dem til en bestemt hendelse. Se dokumentasjonen p\u00e5 https://www.home-assistant.io/integrations/doorbird/#events. Eksempel: noen_trykket_knappen, bevegelse" } } diff --git a/homeassistant/components/doorbird/translations/pl.json b/homeassistant/components/doorbird/translations/pl.json index f55d2d406f2..85a7a8765a7 100644 --- a/homeassistant/components/doorbird/translations/pl.json +++ b/homeassistant/components/doorbird/translations/pl.json @@ -29,6 +29,9 @@ "data": { "events": "Lista wydarze\u0144 oddzielona przecinkami" }, + "data_description": { + "events": "Dodaj nazw\u0119 oddzielon\u0105 przecinkami dla ka\u017cdego wydarzenia, kt\u00f3re chcesz \u015bledzi\u0107. Po wprowadzeniu ich tutaj u\u017cyj aplikacji DoorBird, aby przypisa\u0107 je do konkretnego wydarzenia. \n\nPrzyk\u0142ad: kto\u015b_nacisn\u0105\u0142_przycisk, ruch" + }, "description": "Dodaj nazwy wydarze\u0144 oddzielonych przecinkami, kt\u00f3re chcesz \u015bledzi\u0107. Po wprowadzeniu ich tutaj u\u017cyj aplikacji DoorBird, aby przypisa\u0107 je do okre\u015blonych zdarze\u0144. Zapoznaj si\u0119 z dokumentacj\u0105 na stronie https://www.home-assistant.io/integrations/doorbird/#events.\nPrzyk\u0142ad: nacisniecie_przycisku, ruch" } } diff --git a/homeassistant/components/doorbird/translations/pt-BR.json b/homeassistant/components/doorbird/translations/pt-BR.json index 3f2c479df8f..07170c086b7 100644 --- a/homeassistant/components/doorbird/translations/pt-BR.json +++ b/homeassistant/components/doorbird/translations/pt-BR.json @@ -29,6 +29,9 @@ "data": { "events": "Lista de eventos separados por v\u00edrgulas." }, + "data_description": { + "events": "Adicione um nome de evento separado por v\u00edrgula para cada evento que voc\u00ea deseja rastrear. Depois de inseri-los aqui, use o aplicativo DoorBird para atribu\u00ed-los a um evento espec\u00edfico. \n\n Exemplo: someone_pressed_the_button, movimento" + }, "description": "Adicione um nome de evento separado por v\u00edrgula para cada evento que voc\u00ea deseja rastrear. Depois de inseri-los aqui, use o aplicativo DoorBird para atribu\u00ed-los a um evento espec\u00edfico. Consulte a documenta\u00e7\u00e3o em https://www.home-assistant.io/integrations/doorbird/#events. Exemplo: alguem_pressionou_o_botao movimento" } } diff --git a/homeassistant/components/doorbird/translations/ru.json b/homeassistant/components/doorbird/translations/ru.json index df156bb640a..045092a2b97 100644 --- a/homeassistant/components/doorbird/translations/ru.json +++ b/homeassistant/components/doorbird/translations/ru.json @@ -29,6 +29,9 @@ "data": { "events": "\u0421\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e." }, + "data_description": { + "events": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 DoorBird, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u0438\u0445 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044e.\n\n\u041f\u0440\u0438\u043c\u0435\u0440: somebody_pressed_the_button, motion." + }, "description": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 DoorBird, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u0438\u0445 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044e. \u041f\u0440\u0438\u043c\u0435\u0440: somebody_pressed_the_button, motion. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438: https://www.home-assistant.io/integrations/doorbird/#events." } } diff --git a/homeassistant/components/doorbird/translations/tr.json b/homeassistant/components/doorbird/translations/tr.json index 1bb08b6f81e..1e472a43535 100644 --- a/homeassistant/components/doorbird/translations/tr.json +++ b/homeassistant/components/doorbird/translations/tr.json @@ -29,6 +29,9 @@ "data": { "events": "Virg\u00fclle ayr\u0131lm\u0131\u015f olaylar\u0131n listesi." }, + "data_description": { + "events": "\u0130zlemek istedi\u011finiz her etkinlik i\u00e7in virg\u00fclle ayr\u0131lm\u0131\u015f bir etkinlik ad\u0131 ekleyin. Bunlar\u0131 buraya girdikten sonra, onlar\u0131 belirli bir etkinli\u011fe atamak i\u00e7in DoorBird uygulamas\u0131n\u0131 kullan\u0131n. \n\n \u00d6rnek: birisi_butona_basti, hareket" + }, "description": "\u0130zlemek istedi\u011finiz her etkinlik i\u00e7in virg\u00fclle ayr\u0131lm\u0131\u015f bir etkinlik ad\u0131 ekleyin. Bunlar\u0131 buraya girdikten sonra, onlar\u0131 belirli bir etkinli\u011fe atamak i\u00e7in DoorBird uygulamas\u0131n\u0131 kullan\u0131n. https://www.home-assistant.io/integrations/doorbird/#events adresindeki belgelere bak\u0131n. \u00d6rnek: birisi_pressed_the_button, hareket" } } diff --git a/homeassistant/components/doorbird/translations/zh-Hant.json b/homeassistant/components/doorbird/translations/zh-Hant.json index 020267b4921..616e346b0dc 100644 --- a/homeassistant/components/doorbird/translations/zh-Hant.json +++ b/homeassistant/components/doorbird/translations/zh-Hant.json @@ -29,6 +29,9 @@ "data": { "events": "\u4ee5\u9017\u865f\u5206\u5225\u4e8b\u4ef6\u5217\u8868\u3002" }, + "data_description": { + "events": "\u4ee5\u9017\u865f\u5206\u5225\u6240\u8981\u8ffd\u8e64\u7684\u4e8b\u4ef6\u540d\u7a31\u3002\u65bc\u6b64\u8f38\u5165\u5f8c\uff0c\u4f7f\u7528 DoorBird App \u6307\u5b9a\u81f3\u7279\u5b9a\u4e8b\u4ef6\u3002\n\n\u4f8b\u5982\uff1asomebody_pressed_the_button, motion" + }, "description": "\u4ee5\u9017\u865f\u5206\u5225\u6240\u8981\u8ffd\u8e64\u7684\u4e8b\u4ef6\u540d\u7a31\u3002\u65bc\u6b64\u8f38\u5165\u5f8c\uff0c\u4f7f\u7528 DoorBird App \u6307\u5b9a\u81f3\u7279\u5b9a\u4e8b\u4ef6\u3002\u8acb\u53c3\u95b1\u6587\u4ef6\uff1ahttps://www.home-assistant.io/integrations/doorbird/#events\u3002\u4f8b\u5982\uff1asomebody_pressed_the_button, motion" } } diff --git a/homeassistant/components/dsmr/translations/it.json b/homeassistant/components/dsmr/translations/it.json index 9b50979d016..67ca750dd02 100644 --- a/homeassistant/components/dsmr/translations/it.json +++ b/homeassistant/components/dsmr/translations/it.json @@ -9,12 +9,12 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "cannot_communicate": "Impossibile comunicare", "cannot_connect": "Impossibile connettersi", - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "step": { - "one": "Pi\u00f9", - "other": "Altri", + "one": "Vuoto", + "other": "Vuoti", "setup_network": { "data": { "dsmr_version": "Seleziona la versione DSMR", diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index 252cf82df42..d39d148e5a5 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -9,14 +9,7 @@ import voluptuous as vol from homeassistant.components.media_player import ( PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, MediaPlayerEntity, -) -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( @@ -46,12 +39,12 @@ PLATFORM_SCHEMA: Final = PARENT_PLATFORM_SCHEMA.extend( ) DUNEHD_PLAYER_SUPPORT: Final[int] = ( - SUPPORT_PAUSE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY ) diff --git a/homeassistant/components/dunehd/translations/ca.json b/homeassistant/components/dunehd/translations/ca.json index 12f139afe60..08dd841bf4f 100644 --- a/homeassistant/components/dunehd/translations/ca.json +++ b/homeassistant/components/dunehd/translations/ca.json @@ -13,7 +13,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Configura la integraci\u00f3 Dune HD. Si tens problemes durant la configuraci\u00f3, v\u00e9s a: https://www.home-assistant.io/integrations/dunehd\n\nAssegura't que el reproductor estigui engegat.", + "description": "Assegura't que el reproductor est\u00e0 engegat.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/de.json b/homeassistant/components/dunehd/translations/de.json index f3d7ecd725a..bcab02b2a09 100644 --- a/homeassistant/components/dunehd/translations/de.json +++ b/homeassistant/components/dunehd/translations/de.json @@ -13,7 +13,7 @@ "data": { "host": "Host" }, - "description": "Richte die Dune HD-Integration ein. Wenn du Probleme mit der Konfiguration hast, gehe zu: https://www.home-assistant.io/integrations/dunehd \n\nStelle sicher, dass dein Player eingeschaltet ist.", + "description": "Stelle sicher, dass dein Player eingeschaltet ist.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/en.json b/homeassistant/components/dunehd/translations/en.json index 41540c987be..d6392509fcf 100644 --- a/homeassistant/components/dunehd/translations/en.json +++ b/homeassistant/components/dunehd/translations/en.json @@ -13,7 +13,7 @@ "data": { "host": "Host" }, - "description": "Set up Dune HD integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/dunehd \n\nEnsure that your player is turned on.", + "description": "Ensure that your player is turned on.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/et.json b/homeassistant/components/dunehd/translations/et.json index bb53d124200..3f4a9b4b085 100644 --- a/homeassistant/components/dunehd/translations/et.json +++ b/homeassistant/components/dunehd/translations/et.json @@ -13,7 +13,7 @@ "data": { "host": "" }, - "description": "Seadista Dune HD sidumine. Kui seadistamisega on probleeme, mine aadressile https://www.home-assistant.io/integrations/dunehd\n\n Veendu, et seade on sisse l\u00fclitatud.", + "description": "Veendu, et m\u00e4ngija on sisse l\u00fclitatud.", "title": "" } } diff --git a/homeassistant/components/dunehd/translations/fr.json b/homeassistant/components/dunehd/translations/fr.json index 0e8cb6d6ff8..8c3bc89a0e6 100644 --- a/homeassistant/components/dunehd/translations/fr.json +++ b/homeassistant/components/dunehd/translations/fr.json @@ -13,7 +13,7 @@ "data": { "host": "H\u00f4te" }, - "description": "Configurez l'int\u00e9gration Dune HD. Si vous rencontrez des probl\u00e8mes de configuration, acc\u00e9dez \u00e0: https://www.home-assistant.io/integrations/dunehd \n\n Assurez-vous que votre lecteur est allum\u00e9.", + "description": "Assurez-vous que votre lecteur est allum\u00e9.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/hu.json b/homeassistant/components/dunehd/translations/hu.json index 15b2d297363..1dc7c8ec6cc 100644 --- a/homeassistant/components/dunehd/translations/hu.json +++ b/homeassistant/components/dunehd/translations/hu.json @@ -13,7 +13,7 @@ "data": { "host": "C\u00edm" }, - "description": "\u00c1ll\u00edtsa be a Dune HD integr\u00e1ci\u00f3t. Ha probl\u00e9m\u00e1i vannak a konfigur\u00e1ci\u00f3val, l\u00e1togasson el a k\u00f6vetkez\u0151 oldalra: https://www.home-assistant.io/integrations/dunehd \n\n Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a lej\u00e1tsz\u00f3 be van kapcsolva.", + "description": "Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a lej\u00e1tsz\u00f3 be van kapcsolva.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/id.json b/homeassistant/components/dunehd/translations/id.json index 25cb96bedea..5a86947a07a 100644 --- a/homeassistant/components/dunehd/translations/id.json +++ b/homeassistant/components/dunehd/translations/id.json @@ -13,7 +13,7 @@ "data": { "host": "Host" }, - "description": "Siapkan integrasi Dune HD. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/dunehd \n\nPastikan pemutar Anda dinyalakan.", + "description": "Pastikan pemutar Anda dinyalakan.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/it.json b/homeassistant/components/dunehd/translations/it.json index e296a59e474..0e33feb2640 100644 --- a/homeassistant/components/dunehd/translations/it.json +++ b/homeassistant/components/dunehd/translations/it.json @@ -13,7 +13,7 @@ "data": { "host": "Host" }, - "description": "Configura l'integrazione Dune HD. In caso di problemi con la configurazione, visita: https://www.home-assistant.io/integrations/dunehd \n\n Assicurati che il tuo lettore sia acceso.", + "description": "Assicurati che il tuo lettore sia acceso.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/nl.json b/homeassistant/components/dunehd/translations/nl.json index bb3dd7def47..f4c54a5dba5 100644 --- a/homeassistant/components/dunehd/translations/nl.json +++ b/homeassistant/components/dunehd/translations/nl.json @@ -13,7 +13,7 @@ "data": { "host": "Host" }, - "description": "Stel Dune HD integratie in. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/dunehd \n\nZorg ervoor dat uw speler is ingeschakeld.", + "description": "Zorg ervoor dat uw speler is ingeschakeld.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/no.json b/homeassistant/components/dunehd/translations/no.json index a887228c322..1b35f5581b1 100644 --- a/homeassistant/components/dunehd/translations/no.json +++ b/homeassistant/components/dunehd/translations/no.json @@ -13,7 +13,7 @@ "data": { "host": "Vert" }, - "description": "Konfigurer Dune HD-integrering. Hvis du har problemer med konfigurasjonen, kan du g\u00e5 til: https://www.home-assistant.io/integrations/dunehd \n\nKontroller at spilleren er sl\u00e5tt p\u00e5.", + "description": "S\u00f8rg for at spilleren er sl\u00e5tt p\u00e5.", "title": "" } } diff --git a/homeassistant/components/dunehd/translations/pl.json b/homeassistant/components/dunehd/translations/pl.json index 5def31d1c46..376b84e05d9 100644 --- a/homeassistant/components/dunehd/translations/pl.json +++ b/homeassistant/components/dunehd/translations/pl.json @@ -13,7 +13,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Konfiguracja integracji odtwarzacza Dune HD. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a do strony: https://www.home-assistant.io/integrations/dunehd\n\nUpewnij si\u0119, \u017ce odtwarzacz jest w\u0142\u0105czony.", + "description": "Upewnij si\u0119, \u017ce odtwarzacz jest w\u0142\u0105czony.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/pt-BR.json b/homeassistant/components/dunehd/translations/pt-BR.json index 072cf6011ea..4775f1af379 100644 --- a/homeassistant/components/dunehd/translations/pt-BR.json +++ b/homeassistant/components/dunehd/translations/pt-BR.json @@ -13,7 +13,7 @@ "data": { "host": "Nome do host" }, - "description": "Configure a integra\u00e7\u00e3o Dune HD. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/dunehd \n\n Certifique-se de que seu player est\u00e1 ligado.", + "description": "Certifique-se de que seu player est\u00e1 ligado.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/ru.json b/homeassistant/components/dunehd/translations/ru.json index c1537de579f..134511e66f2 100644 --- a/homeassistant/components/dunehd/translations/ru.json +++ b/homeassistant/components/dunehd/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Dune HD. \u0415\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443: https://www.home-assistant.io/integrations/dunehd\n\n\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u0430\u0448 \u043f\u043b\u0435\u0435\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d.", + "description": "\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/tr.json b/homeassistant/components/dunehd/translations/tr.json index 121267c34e3..79c2f033cfc 100644 --- a/homeassistant/components/dunehd/translations/tr.json +++ b/homeassistant/components/dunehd/translations/tr.json @@ -13,7 +13,7 @@ "data": { "host": "Sunucu" }, - "description": "Dune HD entegrasyonunu ayarlay\u0131n. Yap\u0131land\u0131rmayla ilgili sorunlar\u0131n\u0131z varsa \u015fu adrese gidin: https://www.home-assistant.io/integrations/dunehd \n\n Oynat\u0131c\u0131n\u0131z\u0131n a\u00e7\u0131k oldu\u011fundan emin olun.", + "description": "Oynat\u0131c\u0131n\u0131z\u0131n a\u00e7\u0131k oldu\u011fundan emin olun.", "title": "Dune HD" } } diff --git a/homeassistant/components/dunehd/translations/zh-Hant.json b/homeassistant/components/dunehd/translations/zh-Hant.json index a81055b9576..993a6d9a210 100644 --- a/homeassistant/components/dunehd/translations/zh-Hant.json +++ b/homeassistant/components/dunehd/translations/zh-Hant.json @@ -13,7 +13,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8a2d\u5b9a Dune HD \u6574\u5408\u3002\u5047\u5982\u65bc\u8a2d\u5b9a\u904e\u7a0b\u4e2d\u906d\u9047\u56f0\u7136\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/dunehd \n\n\u78ba\u5b9a\u64ad\u653e\u68c4\u5df2\u7d93\u958b\u555f\u3002", + "description": "\u78ba\u5b9a\u64ad\u653e\u5668\u5df2\u7d93\u958b\u555f\u3002", "title": "Dune HD" } } diff --git a/homeassistant/components/dweet/manifest.json b/homeassistant/components/dweet/manifest.json index 078ea0ed211..1637293c67f 100644 --- a/homeassistant/components/dweet/manifest.json +++ b/homeassistant/components/dweet/manifest.json @@ -3,7 +3,7 @@ "name": "dweet.io", "documentation": "https://www.home-assistant.io/integrations/dweet", "requirements": ["dweepy==0.3.0"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_polling", "loggers": ["dweepy"] } diff --git a/homeassistant/components/dynalite/light.py b/homeassistant/components/dynalite/light.py index ee91df1ae98..826506fa9b7 100644 --- a/homeassistant/components/dynalite/light.py +++ b/homeassistant/components/dynalite/light.py @@ -1,6 +1,6 @@ """Support for Dynalite channels as lights.""" -from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -22,6 +22,9 @@ async def async_setup_entry( class DynaliteLight(DynaliteBase, LightEntity): """Representation of a Dynalite Channel as a Home Assistant Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + @property def brightness(self) -> int: """Return the brightness of this light between 0..255.""" @@ -39,8 +42,3 @@ class DynaliteLight(DynaliteBase, LightEntity): async def async_turn_off(self, **kwargs) -> None: """Turn the light off.""" await self._device.async_turn_off(**kwargs) - - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_BRIGHTNESS diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 2318b142daf..3673728c7fa 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -9,25 +9,13 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, FAN_AUTO, FAN_ON, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_NONE, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -35,6 +23,7 @@ from homeassistant.const import ( ATTR_TEMPERATURE, PRECISION_HALVES, PRECISION_TENTHS, + STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, ) @@ -80,29 +69,29 @@ HUMIDIFIER_MANUAL_MODE = "manual" # Order matters, because for reverse mapping we don't want to map HEAT to AUX ECOBEE_HVAC_TO_HASS = collections.OrderedDict( [ - ("heat", HVAC_MODE_HEAT), - ("cool", HVAC_MODE_COOL), - ("auto", HVAC_MODE_HEAT_COOL), - ("off", HVAC_MODE_OFF), - ("auxHeatOnly", HVAC_MODE_HEAT), + ("heat", HVACMode.HEAT), + ("cool", HVACMode.COOL), + ("auto", HVACMode.HEAT_COOL), + ("off", HVACMode.OFF), + ("auxHeatOnly", HVACMode.HEAT), ] ) ECOBEE_HVAC_ACTION_TO_HASS = { # Map to None if we do not know how to represent. - "heatPump": CURRENT_HVAC_HEAT, - "heatPump2": CURRENT_HVAC_HEAT, - "heatPump3": CURRENT_HVAC_HEAT, - "compCool1": CURRENT_HVAC_COOL, - "compCool2": CURRENT_HVAC_COOL, - "auxHeat1": CURRENT_HVAC_HEAT, - "auxHeat2": CURRENT_HVAC_HEAT, - "auxHeat3": CURRENT_HVAC_HEAT, - "fan": CURRENT_HVAC_FAN, + "heatPump": HVACAction.HEATING, + "heatPump2": HVACAction.HEATING, + "heatPump3": HVACAction.HEATING, + "compCool1": HVACAction.COOLING, + "compCool2": HVACAction.COOLING, + "auxHeat1": HVACAction.HEATING, + "auxHeat2": HVACAction.HEATING, + "auxHeat3": HVACAction.HEATING, + "fan": HVACAction.FAN, "humidifier": None, - "dehumidifier": CURRENT_HVAC_DRY, - "ventilator": CURRENT_HVAC_FAN, - "economizer": CURRENT_HVAC_FAN, + "dehumidifier": HVACAction.DRYING, + "ventilator": HVACAction.FAN, + "economizer": HVACAction.FAN, "compHotWater": None, "auxHotWater": None, } @@ -169,11 +158,11 @@ SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema( SUPPORT_FLAGS = ( - SUPPORT_TARGET_TEMPERATURE - | SUPPORT_PRESET_MODE - | SUPPORT_AUX_HEAT - | SUPPORT_TARGET_TEMPERATURE_RANGE - | SUPPORT_FAN_MODE + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.AUX_HEAT + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + | ClimateEntityFeature.FAN_MODE ) @@ -320,19 +309,19 @@ class Thermostat(ClimateEntity): self.thermostat = thermostat self._name = self.thermostat["name"] self.vacation = None - self._last_active_hvac_mode = HVAC_MODE_HEAT_COOL + self._last_active_hvac_mode = HVACMode.HEAT_COOL self._operation_list = [] if ( self.thermostat["settings"]["heatStages"] or self.thermostat["settings"]["hasHeatPump"] ): - self._operation_list.append(HVAC_MODE_HEAT) + self._operation_list.append(HVACMode.HEAT) if self.thermostat["settings"]["coolStages"]: - self._operation_list.append(HVAC_MODE_COOL) + self._operation_list.append(HVACMode.COOL) if len(self._operation_list) == 2: - self._operation_list.insert(0, HVAC_MODE_HEAT_COOL) - self._operation_list.append(HVAC_MODE_OFF) + self._operation_list.insert(0, HVACMode.HEAT_COOL) + self._operation_list.append(HVACMode.OFF) self._preset_modes = { comfort["climateRef"]: comfort["name"] @@ -349,7 +338,7 @@ class Thermostat(ClimateEntity): else: await self.data.update() self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) - if self.hvac_mode != HVAC_MODE_OFF: + if self.hvac_mode != HVACMode.OFF: self._last_active_hvac_mode = self.hvac_mode @property @@ -361,7 +350,7 @@ class Thermostat(ClimateEntity): def supported_features(self): """Return the list of supported features.""" if self.has_humidifier_control: - return SUPPORT_FLAGS | SUPPORT_TARGET_HUMIDITY + return SUPPORT_FLAGS | ClimateEntityFeature.TARGET_HUMIDITY return SUPPORT_FLAGS @property @@ -409,14 +398,14 @@ class Thermostat(ClimateEntity): @property def target_temperature_low(self) -> float | None: """Return the lower bound temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self.thermostat["runtime"]["desiredHeat"] / 10.0 return None @property def target_temperature_high(self) -> float | None: """Return the upper bound temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self.thermostat["runtime"]["desiredCool"] / 10.0 return None @@ -453,11 +442,11 @@ class Thermostat(ClimateEntity): @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return None - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return self.thermostat["runtime"]["desiredHeat"] / 10.0 - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return self.thermostat["runtime"]["desiredCool"] / 10.0 return None @@ -466,7 +455,7 @@ class Thermostat(ClimateEntity): """Return the current fan status.""" if "fan" in self.thermostat["equipmentStatus"]: return STATE_ON - return HVAC_MODE_OFF + return STATE_OFF @property def fan_mode(self): @@ -527,7 +516,7 @@ class Thermostat(ClimateEntity): We are unable to map all actions to HA equivalents. """ if self.thermostat["equipmentStatus"] == "": - return CURRENT_HVAC_IDLE + return HVACAction.IDLE actions = [ ECOBEE_HVAC_ACTION_TO_HASS[status] @@ -536,15 +525,15 @@ class Thermostat(ClimateEntity): ] for action in ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, + HVACAction.HEATING, + HVACAction.COOLING, + HVACAction.DRYING, + HVACAction.FAN, ): if action in actions: return action - return CURRENT_HVAC_IDLE + return HVACAction.IDLE @property def extra_state_attributes(self): @@ -691,7 +680,7 @@ class Thermostat(ClimateEntity): heatCoolMinDelta property. https://www.ecobee.com/home/developer/api/examples/ex5.shtml """ - if self.hvac_mode in (HVAC_MODE_HEAT, HVAC_MODE_COOL): + if self.hvac_mode in (HVACMode.HEAT, HVACMode.COOL): heat_temp = temp cool_temp = temp else: @@ -706,7 +695,7 @@ class Thermostat(ClimateEntity): high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) temp = kwargs.get(ATTR_TEMPERATURE) - if self.hvac_mode == HVAC_MODE_HEAT_COOL and ( + if self.hvac_mode == HVACMode.HEAT_COOL and ( low_temp is not None or high_temp is not None ): self.set_auto_temp_hold(low_temp, high_temp) diff --git a/homeassistant/components/ecobee/humidifier.py b/homeassistant/components/ecobee/humidifier.py index f2e9fa1528c..230e456dc60 100644 --- a/homeassistant/components/ecobee/humidifier.py +++ b/homeassistant/components/ecobee/humidifier.py @@ -3,12 +3,15 @@ from __future__ import annotations from datetime import timedelta -from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) from homeassistant.components.humidifier.const import ( DEFAULT_MAX_HUMIDITY, DEFAULT_MIN_HUMIDITY, MODE_AUTO, - SUPPORT_MODES, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -42,6 +45,8 @@ async def async_setup_entry( class EcobeeHumidifier(HumidifierEntity): """A humidifier class for an ecobee thermostat with humidifier attached.""" + _attr_supported_features = HumidifierEntityFeature.MODES + def __init__(self, data, thermostat_index): """Initialize ecobee humidifier platform.""" self.data = data @@ -125,11 +130,6 @@ class EcobeeHumidifier(HumidifierEntity): """Return the current mode, e.g., off, auto, manual.""" return self.thermostat["settings"]["humidifierMode"] - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_MODES - @property def target_humidity(self) -> int: """Return the desired humidity set point.""" diff --git a/homeassistant/components/ecobee/translations/hu.json b/homeassistant/components/ecobee/translations/hu.json index a91478ff038..a2fdd5553a4 100644 --- a/homeassistant/components/ecobee/translations/hu.json +++ b/homeassistant/components/ecobee/translations/hu.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "K\u00e9rj\u00fck, enged\u00e9lyezze ezt az alkalmaz\u00e1st a https://www.ecobee.com/consumerportal/index.html c\u00edmen a k\u00f6vetkez\u0151 PIN-k\u00f3ddal: \n\n {pin} \n \n Ezut\u00e1n nyomja meg a K\u00fcld\u00e9s gombot.", + "description": "K\u00e9rj\u00fck, enged\u00e9lyezze ezt az alkalmaz\u00e1st a https://www.ecobee.com/consumerportal/index.html c\u00edmen a k\u00f6vetkez\u0151 PIN-k\u00f3ddal: \n\n {pin} \n \nEzut\u00e1n nyomja meg a Mehet gombot.", "title": "Alkalmaz\u00e1s enged\u00e9lyez\u00e9se ecobee.com-on" }, "user": { diff --git a/homeassistant/components/econet/climate.py b/homeassistant/components/econet/climate.py index 307b3f07944..16dd4e043dc 100644 --- a/homeassistant/components/econet/climate.py +++ b/homeassistant/components/econet/climate.py @@ -10,16 +10,8 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE @@ -30,11 +22,11 @@ from . import EcoNetEntity from .const import DOMAIN, EQUIPMENT ECONET_STATE_TO_HA = { - ThermostatOperationMode.HEATING: HVAC_MODE_HEAT, - ThermostatOperationMode.COOLING: HVAC_MODE_COOL, - ThermostatOperationMode.OFF: HVAC_MODE_OFF, - ThermostatOperationMode.AUTO: HVAC_MODE_HEAT_COOL, - ThermostatOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY, + ThermostatOperationMode.HEATING: HVACMode.HEAT, + ThermostatOperationMode.COOLING: HVACMode.COOL, + ThermostatOperationMode.OFF: HVACMode.OFF, + ThermostatOperationMode.AUTO: HVACMode.HEAT_COOL, + ThermostatOperationMode.FAN_ONLY: HVACMode.FAN_ONLY, } HA_STATE_TO_ECONET = {value: key for key, value in ECONET_STATE_TO_HA.items()} @@ -47,10 +39,10 @@ ECONET_FAN_STATE_TO_HA = { HA_FAN_STATE_TO_ECONET = {value: key for key, value in ECONET_FAN_STATE_TO_HA.items()} SUPPORT_FLAGS_THERMOSTAT = ( - SUPPORT_TARGET_TEMPERATURE - | SUPPORT_TARGET_TEMPERATURE_RANGE - | SUPPORT_FAN_MODE - | SUPPORT_AUX_HEAT + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.AUX_HEAT ) @@ -90,7 +82,7 @@ class EcoNetThermostat(EcoNetEntity, ClimateEntity): def supported_features(self): """Return the list of supported features.""" if self._econet.supports_humidifier: - return SUPPORT_FLAGS_THERMOSTAT | SUPPORT_TARGET_HUMIDITY + return SUPPORT_FLAGS_THERMOSTAT | ClimateEntityFeature.TARGET_HUMIDITY return SUPPORT_FLAGS_THERMOSTAT @property @@ -113,23 +105,23 @@ class EcoNetThermostat(EcoNetEntity, ClimateEntity): @property def target_temperature(self): """Return the temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return self._econet.cool_set_point - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return self._econet.heat_set_point return None @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self._econet.heat_set_point return None @property def target_temperature_high(self): """Return the higher bound temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self._econet.cool_set_point return None @@ -163,7 +155,7 @@ class EcoNetThermostat(EcoNetEntity, ClimateEntity): Needs to be one of HVAC_MODE_*. """ econet_mode = self._econet.mode - _current_op = HVAC_MODE_OFF + _current_op = HVACMode.OFF if econet_mode is not None: _current_op = ECONET_STATE_TO_HA[econet_mode] diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py index 741d2ee194a..79b821c6cba 100644 --- a/homeassistant/components/econet/water_heater.py +++ b/homeassistant/components/econet/water_heater.py @@ -11,10 +11,8 @@ from homeassistant.components.water_heater import ( STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_PERFORMANCE, - SUPPORT_AWAY_MODE, - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF @@ -37,7 +35,10 @@ ECONET_STATE_TO_HA = { } HA_STATE_TO_ECONET = {value: key for key, value in ECONET_STATE_TO_HA.items()} -SUPPORT_FLAGS_HEATER = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE +SUPPORT_FLAGS_HEATER = ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE +) async def async_setup_entry( @@ -108,11 +109,14 @@ class EcoNetWaterHeater(EcoNetEntity, WaterHeaterEntity): """Return the list of supported features.""" if self.water_heater.modes: if self.water_heater.supports_away: - return SUPPORT_FLAGS_HEATER | SUPPORT_AWAY_MODE + return SUPPORT_FLAGS_HEATER | WaterHeaterEntityFeature.AWAY_MODE return SUPPORT_FLAGS_HEATER if self.water_heater.supports_away: - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE - return SUPPORT_TARGET_TEMPERATURE + return ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.AWAY_MODE + ) + return WaterHeaterEntityFeature.TARGET_TEMPERATURE def set_temperature(self, **kwargs): """Set new target temperature.""" diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index ab01a7998d3..8557658d128 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -5,19 +5,7 @@ import logging import sucks -from homeassistant.components.vacuum import ( - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_STATUS, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - VacuumEntity, -) +from homeassistant.components.vacuum import VacuumEntity, VacuumEntityFeature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.icon import icon_for_battery_level @@ -27,19 +15,6 @@ from . import ECOVACS_DEVICES _LOGGER = logging.getLogger(__name__) -SUPPORT_ECOVACS = ( - SUPPORT_BATTERY - | SUPPORT_RETURN_HOME - | SUPPORT_CLEAN_SPOT - | SUPPORT_STOP - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_LOCATE - | SUPPORT_STATUS - | SUPPORT_SEND_COMMAND - | SUPPORT_FAN_SPEED -) - ATTR_ERROR = "error" ATTR_COMPONENT_PREFIX = "component_" @@ -61,17 +36,31 @@ def setup_platform( class EcovacsVacuum(VacuumEntity): """Ecovacs Vacuums such as Deebot.""" - def __init__(self, device): + _attr_fan_speed_list = [sucks.FAN_SPEED_NORMAL, sucks.FAN_SPEED_HIGH] + _attr_should_poll = False + _attr_supported_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.STOP + | VacuumEntityFeature.TURN_OFF + | VacuumEntityFeature.TURN_ON + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.FAN_SPEED + ) + + def __init__(self, device: sucks.VacBot) -> None: """Initialize the Ecovacs Vacuum.""" self.device = device self.device.connect_and_wait_until_ready() if self.device.vacuum.get("nick") is not None: - self._name = str(self.device.vacuum["nick"]) + self._attr_name = str(self.device.vacuum["nick"]) else: # In case there is no nickname defined, use the device id - self._name = str(format(self.device.vacuum["did"])) + self._attr_name = str(format(self.device.vacuum["did"])) - self._fan_speed = None self._error = None _LOGGER.debug("Vacuum initialized: %s", self.name) @@ -98,11 +87,6 @@ class EcovacsVacuum(VacuumEntity): ) self.schedule_update_ha_state() - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state.""" - return False - @property def unique_id(self) -> str: """Return an unique ID.""" @@ -118,16 +102,6 @@ class EcovacsVacuum(VacuumEntity): """Return true if vacuum is currently charging.""" return self.device.is_charging - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def supported_features(self): - """Flag vacuum cleaner robot features that are supported.""" - return SUPPORT_ECOVACS - @property def status(self): """Return the status of the vacuum cleaner.""" @@ -139,14 +113,14 @@ class EcovacsVacuum(VacuumEntity): self.device.run(sucks.Charge()) @property - def battery_icon(self): + def battery_icon(self) -> str: """Return the battery icon for the vacuum cleaner.""" return icon_for_battery_level( battery_level=self.battery_level, charging=self.is_charging ) @property - def battery_level(self): + def battery_level(self) -> int | None: """Return the battery level of the vacuum cleaner.""" if self.device.battery_status is not None: return self.device.battery_status * 100 @@ -154,16 +128,10 @@ class EcovacsVacuum(VacuumEntity): return super().battery_level @property - def fan_speed(self): + def fan_speed(self) -> str | None: """Return the fan speed of the vacuum cleaner.""" return self.device.fan_speed - @property - def fan_speed_list(self): - """Get the list of available fan speed steps of the vacuum cleaner.""" - - return [sucks.FAN_SPEED_NORMAL, sucks.FAN_SPEED_HIGH] - def turn_on(self, **kwargs): """Turn the vacuum on and start cleaning.""" diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index 777fccaf4a8..2e2abe1fc87 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -6,10 +6,7 @@ import logging import requests import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, @@ -66,6 +63,11 @@ def setup_platform( class EgardiaAlarm(alarm.AlarmControlPanelEntity): """Representation of a Egardia alarm.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + def __init__( self, name, egardiasystem, rs_enabled=False, rs_codes=None, rs_port=52010 ): @@ -93,11 +95,6 @@ class EgardiaAlarm(alarm.AlarmControlPanelEntity): """Return the state of the device.""" return self._status - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - @property def should_poll(self): """Poll if no report server is enabled.""" diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index d9f73963870..8b9a7bce7e1 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -9,8 +9,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -78,21 +77,17 @@ class ElgatoLight( super().__init__(client, info, mac) CoordinatorEntity.__init__(self, coordinator) - min_mired = 143 - max_mired = 344 - supported_color_modes = {COLOR_MODE_COLOR_TEMP} + self._attr_min_mireds = 143 + self._attr_max_mireds = 344 + self._attr_name = info.display_name or info.product_name + self._attr_supported_color_modes = {ColorMode.COLOR_TEMP} + self._attr_unique_id = info.serial_number # Elgato Light supporting color, have a different temperature range if settings.power_on_hue is not None: - supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS} - min_mired = 153 - max_mired = 285 - - self._attr_max_mireds = max_mired - self._attr_min_mireds = min_mired - self._attr_name = info.display_name or info.product_name - self._attr_supported_color_modes = supported_color_modes - self._attr_unique_id = info.serial_number + self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} + self._attr_min_mireds = 153 + self._attr_max_mireds = 285 @property def brightness(self) -> int | None: @@ -108,9 +103,9 @@ class ElgatoLight( def color_mode(self) -> str | None: """Return the color mode of the light.""" if self.coordinator.data.hue is not None: - return COLOR_MODE_HS + return ColorMode.HS - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP @property def hs_color(self) -> tuple[float, float] | None: @@ -151,8 +146,8 @@ class ElgatoLight( and ATTR_HS_COLOR not in kwargs and ATTR_COLOR_TEMP not in kwargs and self.supported_color_modes - and COLOR_MODE_HS in self.supported_color_modes - and self.color_mode == COLOR_MODE_COLOR_TEMP + and ColorMode.HS in self.supported_color_modes + and self.color_mode == ColorMode.COLOR_TEMP ): temperature = self.color_temp diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 2536e5a8de0..22dd50499d6 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -5,11 +5,12 @@ import asyncio import logging import re from types import MappingProxyType -from typing import Any +from typing import Any, cast from urllib.parse import urlparse import async_timeout -import elkm1_lib as elkm1 +from elkm1_lib.elements import Element +from elkm1_lib.elk import Elk import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -22,6 +23,7 @@ from homeassistant.const import ( CONF_PREFIX, CONF_TEMPERATURE_UNIT, CONF_USERNAME, + CONF_ZONE, TEMP_CELSIUS, TEMP_FAHRENHEIT, Platform, @@ -50,7 +52,6 @@ from .const import ( CONF_SETTING, CONF_TASK, CONF_THERMOSTAT, - CONF_ZONE, DISCOVER_SCAN_TIMEOUT, DISCOVERY_INTERVAL, DOMAIN, @@ -92,7 +93,7 @@ SET_TIME_SERVICE_SCHEMA = vol.Schema( ) -def _host_validator(config): +def _host_validator(config: dict[str, str]) -> dict[str, str]: """Validate that a host is properly configured.""" if config[CONF_HOST].startswith("elks://"): if CONF_USERNAME not in config or CONF_PASSWORD not in config: @@ -104,14 +105,14 @@ def _host_validator(config): return config -def _elk_range_validator(rng): - def _housecode_to_int(val): +def _elk_range_validator(rng: str) -> tuple[int, int]: + def _housecode_to_int(val: str) -> int: match = re.search(r"^([a-p])(0[1-9]|1[0-6]|[1-9])$", val.lower()) if match: return (ord(match.group(1)) - ord("a")) * 16 + int(match.group(2)) raise vol.Invalid("Invalid range") - def _elk_value(val): + def _elk_value(val: str) -> int: return int(val) if val.isdigit() else _housecode_to_int(val) vals = [s.strip() for s in str(rng).split("-")] @@ -120,7 +121,7 @@ def _elk_range_validator(rng): return (start, end) -def _has_all_unique_prefixes(value): +def _has_all_unique_prefixes(value: list[dict[str, str]]) -> list[dict[str, str]]: """Validate that each m1 configured has a unique prefix. Uniqueness is determined case-independently. @@ -214,10 +215,13 @@ async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: @callback -def _async_find_matching_config_entry(hass, prefix): +def _async_find_matching_config_entry( + hass: HomeAssistant, prefix: str +) -> ConfigEntry | None: for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == prefix: return entry + return None async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -253,7 +257,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.error("Config item: %s; %s", item, err) return False - elk = elkm1.Elk( + elk = Elk( { "url": conf[CONF_HOST], "userid": conf[CONF_USERNAME], @@ -262,7 +266,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) elk.connect() - def _element_changed(element, changeset): + def _element_changed(element: Element, changeset: dict[str, Any]) -> None: if (keypress := changeset.get("last_keypress")) is None: return @@ -275,7 +279,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: }, ) - for keypad in elk.keypads: # pylint: disable=no-member + for keypad in elk.keypads: keypad.add_callback(_element_changed) try: @@ -284,7 +288,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except asyncio.TimeoutError as exc: raise ConfigEntryNotReady(f"Timed out connecting to {conf[CONF_HOST]}") from exc - elk_temp_unit = elk.panel.temperature_units # pylint: disable=no-member + elk_temp_unit = elk.panel.temperature_units temperature_unit = TEMP_CELSIUS if elk_temp_unit == "C" else TEMP_FAHRENHEIT config["temperature_unit"] = temperature_unit hass.data[DOMAIN][entry.entry_id] = { @@ -301,18 +305,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -def _included(ranges, set_to, values): +def _included(ranges: list[tuple[int, int]], set_to: bool, values: list[bool]) -> None: for rng in ranges: if not rng[0] <= rng[1] <= len(values): raise vol.Invalid(f"Invalid range {rng}") values[rng[0] - 1 : rng[1]] = [set_to] * (rng[1] - rng[0] + 1) -def _find_elk_by_prefix(hass, prefix): +def _find_elk_by_prefix(hass: HomeAssistant, prefix: str) -> Elk | None: """Search all config entries for a given prefix.""" for entry_id in hass.data[DOMAIN]: if hass.data[DOMAIN][entry_id]["prefix"] == prefix: - return hass.data[DOMAIN][entry_id]["elk"] + return cast(Elk, hass.data[DOMAIN][entry_id]["elk"]) + return None async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -329,7 +334,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_wait_for_elk_to_sync( - elk: elkm1.Elk, + elk: Elk, login_timeout: int, sync_timeout: int, ) -> bool: @@ -338,7 +343,9 @@ async def async_wait_for_elk_to_sync( sync_event = asyncio.Event() login_event = asyncio.Event() - def login_status(succeeded): + success = True + + def login_status(succeeded: bool) -> None: nonlocal success success = succeeded @@ -351,10 +358,9 @@ async def async_wait_for_elk_to_sync( login_event.set() sync_event.set() - def sync_complete(): + def sync_complete() -> None: sync_event.set() - success = True elk.add_handler("login", login_status) elk.add_handler("sync_complete", sync_complete) for name, event, timeout in ( @@ -374,8 +380,8 @@ async def async_wait_for_elk_to_sync( return success -def _create_elk_services(hass): - def _getelk(service): +def _create_elk_services(hass: HomeAssistant) -> None: + def _getelk(service: ServiceCall) -> Elk: prefix = service.data["prefix"] elk = _find_elk_by_prefix(hass, prefix) if elk is None: @@ -402,12 +408,18 @@ def _create_elk_services(hass): ) -def create_elk_entities(elk_data, elk_elements, element_type, class_, entities): +def create_elk_entities( + elk_data: dict[str, Any], + elk_elements: list[Element], + element_type: str, + class_: Any, + entities: list[ElkEntity], +) -> list[ElkEntity] | None: """Create the ElkM1 devices of a particular class.""" auto_configure = elk_data["auto_configure"] if not auto_configure and not elk_data["config"][element_type]["enabled"]: - return + return None elk = elk_data["elk"] _LOGGER.debug("Creating elk entities for %s", elk) @@ -427,14 +439,14 @@ def create_elk_entities(elk_data, elk_elements, element_type, class_, entities): class ElkEntity(Entity): """Base class for all Elk entities.""" - def __init__(self, element, elk, elk_data): + def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: """Initialize the base of all Elk devices.""" self._elk = elk self._element = element self._mac = elk_data["mac"] self._prefix = elk_data["prefix"] self._name_prefix = f"{self._prefix} " if self._prefix else "" - self._temperature_unit = elk_data["config"]["temperature_unit"] + self._temperature_unit: str = elk_data["config"]["temperature_unit"] # unique_id starts with elkm1_ iff there is no prefix # it starts with elkm1m_{prefix} iff there is a prefix # this is to avoid a conflict between @@ -450,12 +462,12 @@ class ElkEntity(Entity): self._unique_id = f"{uid_start}_{self._element.default_name('_')}".lower() @property - def name(self): + def name(self) -> str: """Name of the element.""" return f"{self._name_prefix}{self._element.name}" @property - def unique_id(self): + def unique_id(self) -> str: """Return unique id of the element.""" return self._unique_id @@ -465,31 +477,31 @@ class ElkEntity(Entity): return False @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the default attributes of the element.""" return {**self._element.as_dict(), **self.initial_attrs()} @property - def available(self): + def available(self) -> bool: """Is the entity available to be updated.""" return self._elk.is_connected() - def initial_attrs(self): + def initial_attrs(self) -> dict[str, Any]: """Return the underlying element's attributes as a dict.""" attrs = {} attrs["index"] = self._element.index + 1 return attrs - def _element_changed(self, element, changeset): + def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None: pass @callback - def _element_callback(self, element, changeset): + def _element_callback(self, element: Element, changeset: dict[str, Any]) -> None: """Handle callback from an Elk element that has changed.""" self._element_changed(element, changeset) self.async_write_ha_state() - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Register callback for ElkM1 changes and update entity state.""" self._element.add_callback(self._element_callback) self._element_callback(self._element, {}) diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index e9b1c5a3ccb..49aa6f68a09 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -1,19 +1,20 @@ """Each ElkM1 area will be created as a separate alarm_control_panel.""" from __future__ import annotations +from typing import Any + +from elkm1_lib.areas import Area from elkm1_lib.const import AlarmState, ArmedStatus, ArmLevel, ArmUpState -from elkm1_lib.util import username +from elkm1_lib.elements import Element +from elkm1_lib.elk import Elk +from elkm1_lib.keypads import Keypad import voluptuous as vol from homeassistant.components.alarm_control_panel import ( ATTR_CHANGED_BY, - FORMAT_NUMBER, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -31,7 +32,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity -from . import ElkAttachedEntity, create_elk_entities +from . import ElkAttachedEntity, ElkEntity, create_elk_entities from .const import ( ATTR_CHANGED_BY_ID, ATTR_CHANGED_BY_KEYPAD, @@ -66,7 +67,7 @@ async def async_setup_entry( """Set up the ElkM1 alarm platform.""" elk_data = hass.data[DOMAIN][config_entry.entry_id] elk = elk_data["elk"] - entities: list[ElkArea] = [] + entities: list[ElkEntity] = [] create_elk_entities(elk_data, elk.areas, "area", ElkArea, entities) async_add_entities(entities, True) @@ -107,17 +108,24 @@ async def async_setup_entry( class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): """Representation of an Area / Partition within the ElkM1 alarm panel.""" - def __init__(self, element, elk, elk_data): + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + ) + _element: Area + + def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: """Initialize Area as Alarm Control Panel.""" super().__init__(element, elk, elk_data) self._elk = elk - self._changed_by_keypad = None - self._changed_by_time = None - self._changed_by_id = None - self._changed_by = None - self._state = None + self._changed_by_keypad: str | None = None + self._changed_by_time: str | None = None + self._changed_by_id: int | None = None + self._changed_by: str | None = None + self._state: str | None = None - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Register callback for ElkM1 changes.""" await super().async_added_to_hass() if len(self._elk.areas.elements) == 1: @@ -138,45 +146,41 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): if ATTR_CHANGED_BY in last_state.attributes: self._changed_by = last_state.attributes[ATTR_CHANGED_BY] - def _watch_keypad(self, keypad, changeset): + def _watch_keypad(self, keypad: Element, changeset: dict[str, Any]) -> None: + assert isinstance(keypad, Keypad) if keypad.area != self._element.index: return if changeset.get("last_user") is not None: self._changed_by_keypad = keypad.name self._changed_by_time = keypad.last_user_time.isoformat() self._changed_by_id = keypad.last_user + 1 - self._changed_by = username(self._elk, keypad.last_user) + self._changed_by = self._elk.users.username(keypad.last_user) self.async_write_ha_state() - def _watch_area(self, area, changeset): + def _watch_area(self, area: Element, changeset: dict[str, Any]) -> None: if not (last_log := changeset.get("last_log")): return # user_number only set for arm/disarm logs - if not last_log.get("user_number"): + if (user_number := last_log.get("user_number")) is None: return self._changed_by_keypad = None - self._changed_by_id = last_log["user_number"] - self._changed_by = username(self._elk, self._changed_by_id - 1) + self._changed_by_id = user_number + self._changed_by = self._elk.users.username(user_number - 1) self._changed_by_time = last_log["timestamp"] self.async_write_ha_state() @property - def code_format(self): + def code_format(self) -> CodeFormat | None: """Return the alarm code format.""" - return FORMAT_NUMBER + return CodeFormat.NUMBER @property - def state(self): + def state(self) -> str | None: """Return the state of the element.""" return self._state @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - - @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Attributes of the area.""" attrs = self.initial_attrs() elmt = self._element @@ -195,11 +199,11 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): return attrs @property - def changed_by(self): + def changed_by(self) -> str | None: """Last change triggered by.""" return self._changed_by - def _element_changed(self, element, changeset): + def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None: elk_state_to_hass_state = { ArmedStatus.DISARMED.value: STATE_ALARM_DISARMED, ArmedStatus.ARMED_AWAY.value: STATE_ALARM_ARMED_AWAY, @@ -212,57 +216,68 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): if self._element.alarm_state is None: self._state = None - elif self._area_is_in_alarm_state(): + elif self._element.alarm_state >= AlarmState.FIRE_ALARM.value: + # Area is in alarm state self._state = STATE_ALARM_TRIGGERED elif self._entry_exit_timer_is_running(): self._state = ( STATE_ALARM_ARMING if self._element.is_exit else STATE_ALARM_PENDING ) - else: + elif self._element.armed_status is not None: self._state = elk_state_to_hass_state[self._element.armed_status] + else: + self._state = None - def _entry_exit_timer_is_running(self): + def _entry_exit_timer_is_running(self) -> bool: return self._element.timer1 > 0 or self._element.timer2 > 0 - def _area_is_in_alarm_state(self): - return self._element.alarm_state >= AlarmState.FIRE_ALARM.value - - async def async_alarm_disarm(self, code=None): + async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - self._element.disarm(int(code)) + if code is not None: + self._element.disarm(int(code)) - async def async_alarm_arm_home(self, code=None): + async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" - self._element.arm(ArmLevel.ARMED_STAY.value, int(code)) + if code is not None: + self._element.arm(ArmLevel.ARMED_STAY.value, int(code)) - async def async_alarm_arm_away(self, code=None): + async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" - self._element.arm(ArmLevel.ARMED_AWAY.value, int(code)) + if code is not None: + self._element.arm(ArmLevel.ARMED_AWAY.value, int(code)) - async def async_alarm_arm_night(self, code=None): + async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" - self._element.arm(ArmLevel.ARMED_NIGHT.value, int(code)) + if code is not None: + self._element.arm(ArmLevel.ARMED_NIGHT.value, int(code)) - async def async_alarm_arm_home_instant(self, code=None): + async def async_alarm_arm_home_instant(self, code: str | None = None) -> None: """Send arm stay instant command.""" - self._element.arm(ArmLevel.ARMED_STAY_INSTANT.value, int(code)) + if code is not None: + self._element.arm(ArmLevel.ARMED_STAY_INSTANT.value, int(code)) - async def async_alarm_arm_night_instant(self, code=None): + async def async_alarm_arm_night_instant(self, code: str | None = None) -> None: """Send arm night instant command.""" - self._element.arm(ArmLevel.ARMED_NIGHT_INSTANT.value, int(code)) + if code is not None: + self._element.arm(ArmLevel.ARMED_NIGHT_INSTANT.value, int(code)) - async def async_alarm_arm_vacation(self, code=None): + async def async_alarm_arm_vacation(self, code: str | None = None) -> None: """Send arm vacation command.""" - self._element.arm(ArmLevel.ARMED_VACATION.value, int(code)) + if code is not None: + self._element.arm(ArmLevel.ARMED_VACATION.value, int(code)) - async def async_display_message(self, clear, beep, timeout, line1, line2): + async def async_display_message( + self, clear: int, beep: bool, timeout: int, line1: str, line2: str + ) -> None: """Display a message on all keypads for the area.""" self._element.display_message(clear, beep, timeout, line1, line2) - async def async_bypass(self, code=None): + async def async_bypass(self, code: str | None = None) -> None: """Bypass all zones in area.""" - self._element.bypass(code) + if code is not None: + self._element.bypass(int(code)) - async def async_clear_bypass(self, code=None): + async def async_clear_bypass(self, code: str | None = None) -> None: """Clear bypass for all zones in area.""" - self._element.clear_bypass(code) + if code is not None: + self._element.clear_bypass(int(code)) diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 84466266d93..7b6de739d4f 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -1,23 +1,24 @@ """Support for control of Elk-M1 connected thermostats.""" from __future__ import annotations +from typing import Any + from elkm1_lib.const import ThermostatFan, ThermostatMode, ThermostatSetting +from elkm1_lib.elements import Element +from elkm1_lib.elk import Elk +from elkm1_lib.thermostats import Thermostat from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + FAN_AUTO, + FAN_ON, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PRECISION_WHOLE, STATE_ON +from homeassistant.const import PRECISION_WHOLE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -25,12 +26,34 @@ from . import ElkEntity, create_elk_entities from .const import DOMAIN SUPPORT_HVAC = [ - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_AUTO, - HVAC_MODE_FAN_ONLY, + HVACMode.OFF, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.HEAT_COOL, + HVACMode.FAN_ONLY, ] +HASS_TO_ELK_HVAC_MODES = { + HVACMode.OFF: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value), + HVACMode.HEAT: (ThermostatMode.HEAT.value, None), + HVACMode.COOL: (ThermostatMode.COOL.value, None), + HVACMode.HEAT_COOL: (ThermostatMode.AUTO.value, None), + HVACMode.FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value), +} +ELK_TO_HASS_HVAC_MODES = { + ThermostatMode.OFF.value: HVACMode.OFF, + ThermostatMode.COOL.value: HVACMode.COOL, + ThermostatMode.HEAT.value: HVACMode.HEAT, + ThermostatMode.EMERGENCY_HEAT.value: HVACMode.HEAT, + ThermostatMode.AUTO.value: HVACMode.HEAT_COOL, +} +HASS_TO_ELK_FAN_MODES = { + FAN_AUTO: (None, ThermostatFan.AUTO.value), + FAN_ON: (None, ThermostatFan.ON.value), +} +ELK_TO_HASS_FAN_MODES = { + ThermostatFan.AUTO.value: FAN_AUTO, + ThermostatFan.ON.value: FAN_ON, +} async def async_setup_entry( @@ -40,7 +63,7 @@ async def async_setup_entry( ) -> None: """Create the Elk-M1 thermostat platform.""" elk_data = hass.data[DOMAIN][config_entry.entry_id] - entities: list[ElkThermostat] = [] + entities: list[ElkEntity] = [] elk = elk_data["elk"] create_elk_entities( elk_data, elk.thermostats, "thermostat", ElkThermostat, entities @@ -51,28 +74,30 @@ async def async_setup_entry( class ElkThermostat(ElkEntity, ClimateEntity): """Representation of an Elk-M1 Thermostat.""" - def __init__(self, element, elk, elk_data): + _attr_supported_features = ( + ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.AUX_HEAT + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) + _element: Thermostat + + def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: """Initialize climate entity.""" super().__init__(element, elk, elk_data) - self._state = None + self._state: str = HVACMode.OFF @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_RANGE - - @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the temperature unit.""" return self._temperature_unit @property - def current_temperature(self): + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._element.current_temp @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we are trying to reach.""" if self._element.mode in ( ThermostatMode.HEAT.value, @@ -84,102 +109,90 @@ class ElkThermostat(ElkEntity, ClimateEntity): return None @property - def target_temperature_high(self): + def target_temperature_high(self) -> float | None: """Return the high target temperature.""" return self._element.cool_setpoint @property - def target_temperature_low(self): + def target_temperature_low(self) -> float | None: """Return the low target temperature.""" return self._element.heat_setpoint @property - def target_temperature_step(self): + def target_temperature_step(self) -> float: """Return the supported step of target temperature.""" return 1 @property - def current_humidity(self): + def current_humidity(self) -> int | None: """Return the current humidity.""" return self._element.humidity @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return current operation ie. heat, cool, idle.""" return self._state @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return SUPPORT_HVAC @property - def precision(self): + def precision(self) -> int: """Return the precision of the system.""" return PRECISION_WHOLE @property - def is_aux_heat(self): + def is_aux_heat(self) -> bool: """Return if aux heater is on.""" return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature supported.""" return 1 @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature supported.""" return 99 @property - def fan_mode(self): + def fan_mode(self) -> str: """Return the fan setting.""" - if self._element.fan == ThermostatFan.AUTO.value: - return HVAC_MODE_AUTO - if self._element.fan == ThermostatFan.ON.value: - return STATE_ON - return None + return ELK_TO_HASS_FAN_MODES[self._element.fan] - def _elk_set(self, mode, fan): + def _elk_set(self, mode: int | None, fan: int | None) -> None: if mode is not None: self._element.set(ThermostatSetting.MODE.value, mode) if fan is not None: self._element.set(ThermostatSetting.FAN.value, fan) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set thermostat operation mode.""" - settings = { - HVAC_MODE_OFF: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value), - HVAC_MODE_HEAT: (ThermostatMode.HEAT.value, None), - HVAC_MODE_COOL: (ThermostatMode.COOL.value, None), - HVAC_MODE_AUTO: (ThermostatMode.AUTO.value, None), - HVAC_MODE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value), - } - self._elk_set(settings[hvac_mode][0], settings[hvac_mode][1]) + thermostat_mode, fan_mode = HASS_TO_ELK_HVAC_MODES[hvac_mode] + self._elk_set(thermostat_mode, fan_mode) - async def async_turn_aux_heat_on(self): + async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" self._elk_set(ThermostatMode.EMERGENCY_HEAT.value, None) - async def async_turn_aux_heat_off(self): + async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" self._elk_set(ThermostatMode.HEAT.value, None) @property - def fan_modes(self): + def fan_modes(self) -> list[str]: """Return the list of available fan modes.""" - return [HVAC_MODE_AUTO, STATE_ON] + return [FAN_AUTO, FAN_ON] - async def async_set_fan_mode(self, fan_mode): + async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" - if fan_mode == HVAC_MODE_AUTO: - self._elk_set(None, ThermostatFan.AUTO.value) - elif fan_mode == STATE_ON: - self._elk_set(None, ThermostatFan.ON.value) + thermostat_mode, elk_fan_mode = HASS_TO_ELK_FAN_MODES[fan_mode] + self._elk_set(thermostat_mode, elk_fan_mode) - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) @@ -188,14 +201,7 @@ class ElkThermostat(ElkEntity, ClimateEntity): if high_temp is not None: self._element.set(ThermostatSetting.COOL_SETPOINT.value, round(high_temp)) - def _element_changed(self, element, changeset): - mode_to_state = { - ThermostatMode.OFF.value: HVAC_MODE_OFF, - ThermostatMode.COOL.value: HVAC_MODE_COOL, - ThermostatMode.HEAT.value: HVAC_MODE_HEAT, - ThermostatMode.EMERGENCY_HEAT.value: HVAC_MODE_HEAT, - ThermostatMode.AUTO.value: HVAC_MODE_AUTO, - } - self._state = mode_to_state.get(self._element.mode) - if self._state == HVAC_MODE_OFF and self._element.fan == ThermostatFan.ON.value: - self._state = HVAC_MODE_FAN_ONLY + def _element_changed(self, element: Element, changeset: Any) -> None: + self._state = ELK_TO_HASS_HVAC_MODES[self._element.mode] + if self._state == HVACMode.OFF and self._element.fan == ThermostatFan.ON.value: + self._state = HVACMode.FAN_ONLY diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index e4f6dc7f083..8675ff45ee7 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -6,8 +6,8 @@ import logging from typing import Any from urllib.parse import urlparse -import elkm1_lib as elkm1 from elkm1_lib.discovery import ElkSystem +from elkm1_lib.elk import Elk import voluptuous as vol from homeassistant import config_entries, exceptions @@ -84,7 +84,7 @@ async def validate_input(data: dict[str, str], mac: str | None) -> dict[str, str if requires_password and (not userid or not password): raise InvalidAuth - elk = elkm1.Elk( + elk = Elk( {"url": url, "userid": userid, "password": password, "element_list": ["panel"]} ) elk.connect() @@ -105,14 +105,14 @@ async def validate_input(data: dict[str, str], mac: str | None) -> dict[str, str return {"title": device_name, CONF_HOST: url, CONF_PREFIX: slugify(prefix)} -def _address_from_discovery(device: ElkSystem): +def _address_from_discovery(device: ElkSystem) -> str: """Append the port only if its non-standard.""" if device.port in STANDARD_PORTS: return device.ip_address return f"{device.ip_address}:{device.port}" -def _make_url_from_data(data): +def _make_url_from_data(data: dict[str, str]) -> str: if host := data.get(CONF_HOST): return host @@ -133,7 +133,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize the elkm1 config flow.""" self._discovered_device: ElkSystem | None = None self._discovered_devices: dict[str, ElkSystem] = {} @@ -267,9 +267,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_discovered_connection(self, user_input=None): + async def async_step_discovered_connection( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle connecting the device when we have a discovery.""" - errors = {} + errors: dict[str, str] | None = {} device = self._discovered_device assert device is not None if user_input is not None: @@ -279,7 +281,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): else: user_input[CONF_PREFIX] = "" errors, result = await self._async_create_or_error(user_input, False) - if not errors: + if result is not None: return result default_proto = PORT_PROTOCOL_MAP.get(device.port, DEFAULT_SECURE_PROTOCOL) @@ -297,9 +299,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=_placeholders_from_device(device), ) - async def async_step_manual_connection(self, user_input=None): + async def async_step_manual_connection( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle connecting the device when we need manual entry.""" - errors = {} + errors: dict[str, str] | None = {} if user_input is not None: # We might be able to discover the device via directed UDP # in case its on another subnet @@ -314,7 +318,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # 2601 if secure is turned on even though they may want insecure user_input[CONF_ADDRESS] = device.ip_address errors, result = await self._async_create_or_error(user_input, False) - if not errors: + if result is not None: return result return self.async_show_form( @@ -332,7 +336,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, user_input): + async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: """Handle import.""" _LOGGER.debug("Elk is importing from yaml") url = _make_url_from_data(user_input) @@ -344,8 +348,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.debug( "Importing is trying to fill unique id from discovery for %s", host ) - if is_ip_address(host) and ( - device := await async_discover_device(self.hass, host) + if ( + host + and is_ip_address(host) + and (device := await async_discover_device(self.hass, host)) ): await self.async_set_unique_id( dr.format_mac(device.mac_address), raise_on_progress=False @@ -355,9 +361,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors, result = await self._async_create_or_error(user_input, True) if errors: return self.async_abort(reason=list(errors.values())[0]) + assert result is not None return result - def _url_already_configured(self, url): + def _url_already_configured(self, url: str) -> bool: """See if we already have a elkm1 matching user input configured.""" existing_hosts = { urlparse(entry.data[CONF_HOST]).hostname diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index e690ad1c575..9e008359e8c 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -1,11 +1,13 @@ """Support for control of ElkM1 lighting (X10, UPB, etc).""" from __future__ import annotations -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from typing import Any + +from elkm1_lib.elements import Element +from elkm1_lib.elk import Elk +from elkm1_lib.lights import Light + +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -21,7 +23,7 @@ async def async_setup_entry( ) -> None: """Set up the Elk light platform.""" elk_data = hass.data[DOMAIN][config_entry.entry_id] - entities: list[ElkLight] = [] + entities: list[ElkEntity] = [] elk = elk_data["elk"] create_elk_entities(elk_data, elk.lights, "plc", ElkLight, entities) async_add_entities(entities, True) @@ -30,34 +32,33 @@ async def async_setup_entry( class ElkLight(ElkEntity, LightEntity): """Representation of an Elk lighting device.""" - def __init__(self, element, elk, elk_data): + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _element: Light + + def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: """Initialize the Elk light.""" super().__init__(element, elk, elk_data) self._brightness = self._element.status @property - def brightness(self): + def brightness(self) -> int: """Get the brightness.""" return self._brightness - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - @property def is_on(self) -> bool: """Get the current brightness.""" return self._brightness != 0 - def _element_changed(self, element, changeset): + def _element_changed(self, element: Element, changeset: Any) -> None: status = self._element.status if self._element.status != 1 else 100 self._brightness = round(status * 2.55) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" self._element.level(round(kwargs.get(ATTR_BRIGHTNESS, 255) / 2.55)) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" self._element.level(0) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index ceea8e92ca5..ad5634c1e9f 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -2,7 +2,7 @@ "domain": "elkm1", "name": "Elk-M1 Control", "documentation": "https://www.home-assistant.io/integrations/elkm1", - "requirements": ["elkm1-lib==1.2.2"], + "requirements": ["elkm1-lib==1.3.5"], "dhcp": [{ "registered_devices": true }, { "macaddress": "00409D*" }], "codeowners": ["@gwww", "@bdraco"], "dependencies": ["network"], diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index 597080d9de5..d8100c5bcb1 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -3,12 +3,14 @@ from __future__ import annotations from typing import Any +from elkm1_lib.tasks import Task + from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkAttachedEntity, create_elk_entities +from . import ElkAttachedEntity, ElkEntity, create_elk_entities from .const import DOMAIN @@ -19,7 +21,7 @@ async def async_setup_entry( ) -> None: """Create the Elk-M1 scene platform.""" elk_data = hass.data[DOMAIN][config_entry.entry_id] - entities: list[ElkTask] = [] + entities: list[ElkEntity] = [] elk = elk_data["elk"] create_elk_entities(elk_data, elk.tasks, "task", ElkTask, entities) async_add_entities(entities, True) @@ -28,6 +30,8 @@ async def async_setup_entry( class ElkTask(ElkAttachedEntity, Scene): """Elk-M1 task as scene.""" + _element: Task + async def async_activate(self, **kwargs: Any) -> None: """Activate the task.""" self._element.activate() diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index c635404fa43..e41d09e2e76 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -1,13 +1,22 @@ """Support for control of ElkM1 sensors.""" from __future__ import annotations +from typing import Any + from elkm1_lib.const import ( SettingFormat, ZoneLogicalStatus, ZonePhysicalStatus, ZoneType, ) -from elkm1_lib.util import pretty_const, username +from elkm1_lib.counters import Counter +from elkm1_lib.elements import Element +from elkm1_lib.elk import Elk +from elkm1_lib.keypads import Keypad +from elkm1_lib.panel import Panel +from elkm1_lib.settings import Setting +from elkm1_lib.util import pretty_const +from elkm1_lib.zones import Zone import voluptuous as vol from homeassistant.components.sensor import SensorEntity @@ -19,7 +28,7 @@ from homeassistant.helpers import entity_platform from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkAttachedEntity, create_elk_entities +from . import ElkAttachedEntity, ElkEntity, create_elk_entities from .const import ATTR_VALUE, DOMAIN, ELK_USER_CODE_SERVICE_SCHEMA SERVICE_SENSOR_COUNTER_REFRESH = "sensor_counter_refresh" @@ -40,7 +49,7 @@ async def async_setup_entry( ) -> None: """Create the Elk-M1 sensor platform.""" elk_data = hass.data[DOMAIN][config_entry.entry_id] - entities: list[ElkSensor] = [] + entities: list[ElkEntity] = [] elk = elk_data["elk"] create_elk_entities(elk_data, elk.counters, "counter", ElkCounter, entities) create_elk_entities(elk_data, elk.keypads, "keypad", ElkKeypad, entities) @@ -73,43 +82,45 @@ async def async_setup_entry( ) -def temperature_to_state(temperature, undefined_temperature): +def temperature_to_state(temperature: int, undefined_temperature: int) -> str | None: """Convert temperature to a state.""" - return temperature if temperature > undefined_temperature else None + return f"{temperature}" if temperature > undefined_temperature else None class ElkSensor(ElkAttachedEntity, SensorEntity): """Base representation of Elk-M1 sensor.""" - def __init__(self, element, elk, elk_data): + def __init__(self, element: Element, elk: Elk, elk_data: dict[str, Any]) -> None: """Initialize the base of all Elk sensors.""" super().__init__(element, elk, elk_data) - self._state = None + self._state: str | None = None @property - def native_value(self): + def native_value(self) -> str | None: """Return the state of the sensor.""" return self._state - async def async_counter_refresh(self): + async def async_counter_refresh(self) -> None: """Refresh the value of a counter from the panel.""" if not isinstance(self, ElkCounter): raise HomeAssistantError("supported only on ElkM1 Counter sensors") self._element.get() - async def async_counter_set(self, value=None): + async def async_counter_set(self, value: int | None = None) -> None: """Set the value of a counter on the panel.""" if not isinstance(self, ElkCounter): raise HomeAssistantError("supported only on ElkM1 Counter sensors") - self._element.set(value) + if value is not None: + self._element.set(value) - async def async_zone_bypass(self, code=None): + async def async_zone_bypass(self, code: int | None = None) -> None: """Bypass zone.""" if not isinstance(self, ElkZone): raise HomeAssistantError("supported only on ElkM1 Zone sensors") - self._element.bypass(code) + if code is not None: + self._element.bypass(code) - async def async_zone_trigger(self): + async def async_zone_trigger(self) -> None: """Trigger zone.""" if not isinstance(self, ElkZone): raise HomeAssistantError("supported only on ElkM1 Zone sensors") @@ -119,47 +130,51 @@ class ElkSensor(ElkAttachedEntity, SensorEntity): class ElkCounter(ElkSensor): """Representation of an Elk-M1 Counter.""" + _element: Counter + @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend.""" return "mdi:numeric" - def _element_changed(self, element, changeset): + def _element_changed(self, _: Element, changeset: Any) -> None: self._state = self._element.value class ElkKeypad(ElkSensor): """Representation of an Elk-M1 Keypad.""" + _element: Keypad + @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the temperature unit.""" return self._temperature_unit @property - def native_unit_of_measurement(self): + def native_unit_of_measurement(self) -> str: """Return the unit of measurement.""" return self._temperature_unit @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend.""" return "mdi:thermometer-lines" @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Attributes of the sensor.""" - attrs = self.initial_attrs() + attrs: dict[str, Any] = self.initial_attrs() attrs["area"] = self._element.area + 1 attrs["temperature"] = self._state attrs["last_user_time"] = self._element.last_user_time.isoformat() attrs["last_user"] = self._element.last_user + 1 attrs["code"] = self._element.code - attrs["last_user_name"] = username(self._elk, self._element.last_user) + attrs["last_user_name"] = self._elk.users.username(self._element.last_user) attrs["last_keypress"] = self._element.last_keypress return attrs - def _element_changed(self, element, changeset): + def _element_changed(self, _: Element, changeset: Any) -> None: self._state = temperature_to_state( self._element.temperature, UNDEFINED_TEMPERATURE ) @@ -169,20 +184,21 @@ class ElkPanel(ElkSensor): """Representation of an Elk-M1 Panel.""" _attr_entity_category = EntityCategory.DIAGNOSTIC + _element: Panel @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend.""" return "mdi:home" @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["system_trouble_status"] = self._element.system_trouble_status return attrs - def _element_changed(self, element, changeset): + def _element_changed(self, _: Element, changeset: Any) -> None: if self._elk.is_connected(): self._state = ( "Paused" if self._element.remote_programming_status else "Connected" @@ -194,18 +210,20 @@ class ElkPanel(ElkSensor): class ElkSetting(ElkSensor): """Representation of an Elk-M1 Setting.""" + _element: Setting + @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend.""" return "mdi:numeric" - def _element_changed(self, element, changeset): + def _element_changed(self, _: Element, changeset: Any) -> None: self._state = self._element.value @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Attributes of the sensor.""" - attrs = self.initial_attrs() + attrs: dict[str, Any] = self.initial_attrs() attrs["value_format"] = SettingFormat(self._element.value_format).name.lower() return attrs @@ -213,8 +231,10 @@ class ElkSetting(ElkSensor): class ElkZone(ElkSensor): """Representation of an Elk-M1 Zone.""" + _element: Zone + @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend.""" zone_icons = { ZoneType.FIRE_ALARM.value: "fire", @@ -240,9 +260,9 @@ class ElkZone(ElkSensor): return f"mdi:{zone_icons.get(self._element.definition, 'alarm-bell')}" @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Attributes of the sensor.""" - attrs = self.initial_attrs() + attrs: dict[str, Any] = self.initial_attrs() attrs["physical_status"] = ZonePhysicalStatus( self._element.physical_status ).name.lower() @@ -255,14 +275,14 @@ class ElkZone(ElkSensor): return attrs @property - def temperature_unit(self): + def temperature_unit(self) -> str | None: """Return the temperature unit.""" if self._element.definition == ZoneType.TEMPERATURE.value: return self._temperature_unit return None @property - def native_unit_of_measurement(self): + def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement.""" if self._element.definition == ZoneType.TEMPERATURE.value: return self._temperature_unit @@ -270,13 +290,13 @@ class ElkZone(ElkSensor): return ELECTRIC_POTENTIAL_VOLT return None - def _element_changed(self, element, changeset): + def _element_changed(self, _: Element, changeset: Any) -> None: if self._element.definition == ZoneType.TEMPERATURE.value: self._state = temperature_to_state( self._element.temperature, UNDEFINED_TEMPERATURE ) elif self._element.definition == ZoneType.ANALOG_ZONE.value: - self._state = self._element.voltage + self._state = f"{self._element.voltage}" else: self._state = pretty_const( ZoneLogicalStatus(self._element.logical_status).name diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index 6c5564975bb..54588958e61 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -1,12 +1,16 @@ """Support for control of ElkM1 outputs (relays).""" from __future__ import annotations +from typing import Any + +from elkm1_lib.outputs import Output + from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkAttachedEntity, create_elk_entities +from . import ElkAttachedEntity, ElkEntity, create_elk_entities from .const import DOMAIN @@ -17,7 +21,7 @@ async def async_setup_entry( ) -> None: """Create the Elk-M1 switch platform.""" elk_data = hass.data[DOMAIN][config_entry.entry_id] - entities: list[ElkOutput] = [] + entities: list[ElkEntity] = [] elk = elk_data["elk"] create_elk_entities(elk_data, elk.outputs, "output", ElkOutput, entities) async_add_entities(entities, True) @@ -26,15 +30,17 @@ async def async_setup_entry( class ElkOutput(ElkAttachedEntity, SwitchEntity): """Elk output as switch.""" + _element: Output + @property def is_on(self) -> bool: """Get the current output status.""" return self._element.output_on - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the output.""" self._element.turn_on(0) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the output.""" self._element.turn_off() diff --git a/homeassistant/components/elkm1/translations/bg.json b/homeassistant/components/elkm1/translations/bg.json index 8fd587a0640..5e83523d419 100644 --- a/homeassistant/components/elkm1/translations/bg.json +++ b/homeassistant/components/elkm1/translations/bg.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "flow_title": "{mac_address} ({host})", "step": { diff --git a/homeassistant/components/elkm1/translations/ca.json b/homeassistant/components/elkm1/translations/ca.json index 2317e475a37..b0a979f98b5 100644 --- a/homeassistant/components/elkm1/translations/ca.json +++ b/homeassistant/components/elkm1/translations/ca.json @@ -4,7 +4,9 @@ "address_already_configured": "Ja hi ha un Elk-M1 configurat amb aquesta adre\u00e7a", "already_configured": "Ja hi ha un Elk-M1 configurat amb aquest prefix", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", - "cannot_connect": "Ha fallat la connexi\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Adre\u00e7a IP, domini o port s\u00e8rie (en cas d'una connexi\u00f3 s\u00e8rie).", - "device": "Dispositiu", - "password": "Contrasenya", - "prefix": "Prefix \u00fanic (deixa-ho en blanc si nom\u00e9s tens un \u00fanic controlador Elk-M1).", - "protocol": "Protocol", - "temperature_unit": "Unitats de temperatura que utilitza l'Elk-M1.", - "username": "Nom d'usuari" + "device": "Dispositiu" }, "description": "Selecciona un sistema descobert o 'entrada manual' si no s'han descobert dispositius.", "title": "Connexi\u00f3 amb el controlador Elk-M1" diff --git a/homeassistant/components/elkm1/translations/cs.json b/homeassistant/components/elkm1/translations/cs.json index f2f4c17af9c..9ff8da3067d 100644 --- a/homeassistant/components/elkm1/translations/cs.json +++ b/homeassistant/components/elkm1/translations/cs.json @@ -28,13 +28,6 @@ "title": "P\u0159ipojen\u00ed k ovlada\u010di Elk-M1" }, "user": { - "data": { - "password": "Heslo", - "prefix": "Jedine\u010dn\u00fd prefix (ponechte pr\u00e1zdn\u00e9, pokud m\u00e1te pouze jeden ElkM1).", - "protocol": "Protokol", - "temperature_unit": "Jednotka teploty pou\u017e\u00edvan\u00e1 ElkM1.", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no" - }, "title": "P\u0159ipojen\u00ed k ovlada\u010di Elk-M1" } } diff --git a/homeassistant/components/elkm1/translations/de.json b/homeassistant/components/elkm1/translations/de.json index cf318a626c9..7c08a9b254d 100644 --- a/homeassistant/components/elkm1/translations/de.json +++ b/homeassistant/components/elkm1/translations/de.json @@ -4,7 +4,9 @@ "address_already_configured": "Ein ElkM1 mit dieser Adresse ist bereits konfiguriert", "already_configured": "Ein ElkM1 mit diesem Pr\u00e4fix ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", - "cannot_connect": "Verbinden fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Die IP-Adresse, die Domain oder der serielle Port bei einer seriellen Verbindung.", - "device": "Ger\u00e4t", - "password": "Passwort", - "prefix": "Ein eindeutiges Pr\u00e4fix (leer lassen, wenn du nur einen ElkM1 hast).", - "protocol": "Protokoll", - "temperature_unit": "Die von ElkM1 verwendete Temperatureinheit.", - "username": "Benutzername" + "device": "Ger\u00e4t" }, "description": "W\u00e4hle ein erkanntes System oder \"Manuelle Eingabe\", wenn keine Ger\u00e4te erkannt wurden.", "title": "Stelle eine Verbindung zur Elk-M1-Steuerung her" diff --git a/homeassistant/components/elkm1/translations/el.json b/homeassistant/components/elkm1/translations/el.json index 9f600806d83..bc1d64649aa 100644 --- a/homeassistant/components/elkm1/translations/el.json +++ b/homeassistant/components/elkm1/translations/el.json @@ -4,7 +4,9 @@ "address_already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_configured": "\u0388\u03bd\u03b1 ElkM1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03ae \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 \u03ae \u03b7 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae \u03b8\u03cd\u03c1\u03b1 \u03b5\u03ac\u03bd \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03ad\u03c3\u03c9 \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2.", - "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", - "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", - "prefix": "\u0388\u03bd\u03b1 \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 (\u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b1\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 ElkM1).", - "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf", - "temperature_unit": "\u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1\u03c2 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf ElkM1.", - "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03ae \"\u039c\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b9\u03c3\u03b7\" \u03b5\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2.", "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Elk-M1 Control" diff --git a/homeassistant/components/elkm1/translations/es-419.json b/homeassistant/components/elkm1/translations/es-419.json index 02271c4ea6c..09b5bc190f7 100644 --- a/homeassistant/components/elkm1/translations/es-419.json +++ b/homeassistant/components/elkm1/translations/es-419.json @@ -11,14 +11,6 @@ }, "step": { "user": { - "data": { - "address": "La direcci\u00f3n IP, dominio o puerto serie si se conecta via serial.", - "password": "Contrase\u00f1a (solo segura).", - "prefix": "Un prefijo \u00fanico (d\u00e9jelo en blanco si solo tiene un ElkM1).", - "protocol": "Protocolo", - "temperature_unit": "La unidad de temperatura que utiliza ElkM1.", - "username": "Nombre de usuario (solo seguro)." - }, "description": "La cadena de direcci\u00f3n debe tener el formato 'direcci\u00f3n[:puerto]' para 'seguro' y 'no seguro'. Ejemplo: '192.168.1.1'. El puerto es opcional y el valor predeterminado es 2101 para 'no seguro' y 2601 para 'seguro'. Para el protocolo serie, la direcci\u00f3n debe estar en la forma 'tty[:baudios]'. Ejemplo: '/dev/ttyS1'. La velocidad en baudios es opcional y su valor predeterminado es 115200.", "title": "Con\u00e9ctese al control Elk-M1" } diff --git a/homeassistant/components/elkm1/translations/es.json b/homeassistant/components/elkm1/translations/es.json index 06c0d9e257e..46e25dd288f 100644 --- a/homeassistant/components/elkm1/translations/es.json +++ b/homeassistant/components/elkm1/translations/es.json @@ -37,13 +37,7 @@ }, "user": { "data": { - "address": "La direcci\u00f3n IP o dominio o puerto serie si se conecta a trav\u00e9s de serie.", - "device": "Dispositivo", - "password": "Contrase\u00f1a", - "prefix": "Un prefijo \u00fanico (d\u00e9jalo en blanco si s\u00f3lo tienes un Elk-M1).", - "protocol": "Protocolo", - "temperature_unit": "La temperatura que usa la unidad Elk-M1", - "username": "Usuario" + "device": "Dispositivo" }, "description": "La cadena de direcci\u00f3n debe estar en el formato 'direcci\u00f3n[:puerto]' para 'seguro' y 'no-seguro'. Ejemplo: '192.168.1.1'. El puerto es opcional y el valor predeterminado es 2101 para 'no-seguro' y 2601 para 'seguro'. Para el protocolo serie, la direcci\u00f3n debe tener la forma 'tty[:baudios]'. Ejemplo: '/dev/ttyS1'. Los baudios son opcionales y el valor predeterminado es 115200.", "title": "Conectar con Control Elk-M1" diff --git a/homeassistant/components/elkm1/translations/et.json b/homeassistant/components/elkm1/translations/et.json index d874763045f..fbefde0a118 100644 --- a/homeassistant/components/elkm1/translations/et.json +++ b/homeassistant/components/elkm1/translations/et.json @@ -4,7 +4,9 @@ "address_already_configured": "Selle aadressiga ElkM1 on juba seadistatud", "already_configured": "Selle eesliitega ElkM1 on juba seadistatud", "already_in_progress": "Seadistamine juba k\u00e4ib", - "cannot_connect": "\u00dchendumine nurjus" + "cannot_connect": "\u00dchendumine nurjus", + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" }, "error": { "cannot_connect": "\u00dchendamine nurjus", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "IP-aadress v\u00f5i domeen v\u00f5i jadaport, kui \u00fchendatakse jadaliidese kaudu.", - "device": "Seade", - "password": "Salas\u00f5na", - "prefix": "Unikaalne eesliide (j\u00e4ta t\u00fchjaks kui on ainult \u00fcks ElkM1).", - "protocol": "Protokoll", - "temperature_unit": "ElkM1'i temperatuuri\u00fchik.", - "username": "Kasutajanimi" + "device": "Seade" }, "description": "Vali avastatud s\u00fcsteem v\u00f5i \"K\u00e4sitsi sisestamine\" kui \u00fchtegi seadet ei ole avastatud.", "title": "\u00dchendu Elk-M1 Control" diff --git a/homeassistant/components/elkm1/translations/fr.json b/homeassistant/components/elkm1/translations/fr.json index 07b5d132968..e7512a596d1 100644 --- a/homeassistant/components/elkm1/translations/fr.json +++ b/homeassistant/components/elkm1/translations/fr.json @@ -4,7 +4,9 @@ "address_already_configured": "Un ElkM1 avec cette adresse est d\u00e9j\u00e0 configur\u00e9", "already_configured": "Un ElkM1 avec ce pr\u00e9fixe est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", - "cannot_connect": "\u00c9chec de connexion" + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" }, "error": { "cannot_connect": "\u00c9chec de connexion", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "L'adresse IP ou le domaine ou le port s\u00e9rie si vous vous connectez via s\u00e9rie.", - "device": "Appareil", - "password": "Mot de passe", - "prefix": "Un pr\u00e9fixe unique (laissez vide si vous n'avez qu'un seul ElkM1).", - "protocol": "Protocole", - "temperature_unit": "L'unit\u00e9 de temp\u00e9rature utilis\u00e9e par ElkM1.", - "username": "Nom d'utilisateur" + "device": "Appareil" }, "description": "Choisissez un syst\u00e8me d\u00e9couvert ou 'Entr\u00e9e manuelle' si aucun appareil n'a \u00e9t\u00e9 d\u00e9couvert.", "title": "Se connecter a Elk-M1 Control" diff --git a/homeassistant/components/elkm1/translations/he.json b/homeassistant/components/elkm1/translations/he.json index 8d7b5c9b945..3dda78332f9 100644 --- a/homeassistant/components/elkm1/translations/he.json +++ b/homeassistant/components/elkm1/translations/he.json @@ -2,7 +2,9 @@ "config": { "abort": { "already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea", - "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", @@ -31,10 +33,7 @@ }, "user": { "data": { - "device": "\u05d4\u05ea\u05e7\u05df", - "password": "\u05e1\u05d9\u05e1\u05de\u05d4", - "protocol": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc", - "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + "device": "\u05d4\u05ea\u05e7\u05df" }, "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05d0\u05dc \u05d1\u05e7\u05e8\u05ea Elk-M1" } diff --git a/homeassistant/components/elkm1/translations/hu.json b/homeassistant/components/elkm1/translations/hu.json index d076ad684c2..d744ac578ab 100644 --- a/homeassistant/components/elkm1/translations/hu.json +++ b/homeassistant/components/elkm1/translations/hu.json @@ -3,8 +3,10 @@ "abort": { "address_already_configured": "Az ElkM1 ezzel a c\u00edmmel m\u00e1r konfigur\u00e1lva van", "already_configured": "Az ezzel az el\u0151taggal rendelkez\u0151 ElkM1 m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Az IP-c\u00edm vagy tartom\u00e1ny vagy soros port, ha soros kapcsolaton kereszt\u00fcl csatlakozik.", - "device": "Eszk\u00f6z", - "password": "Jelsz\u00f3", - "prefix": "Egyedi el\u0151tag (hagyja \u00fcresen, ha csak egy ElkM1 van).", - "protocol": "Protokoll", - "temperature_unit": "Az ElkM1 h\u0151m\u00e9rs\u00e9kleti egys\u00e9g haszn\u00e1lja.", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + "device": "Eszk\u00f6z" }, "description": "V\u00e1lasszon egy felfedezett rendszert vagy a \u201eK\u00e9zi bevitelt\u201d, ha nem \u00e9szlelt eszk\u00f6zt.", "title": "Csatlakoz\u00e1s az Elk-M1 vez\u00e9rl\u0151h\u00f6z" diff --git a/homeassistant/components/elkm1/translations/id.json b/homeassistant/components/elkm1/translations/id.json index 782906fac0a..4512ad040e1 100644 --- a/homeassistant/components/elkm1/translations/id.json +++ b/homeassistant/components/elkm1/translations/id.json @@ -4,7 +4,9 @@ "address_already_configured": "ElkM1 dengan alamat ini sudah dikonfigurasi", "already_configured": "ElkM1 dengan prefiks ini sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", - "cannot_connect": "Gagal terhubung" + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { "cannot_connect": "Gagal terhubung", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Alamat IP atau domain atau port serial jika terhubung melalui serial.", - "device": "Perangkat", - "password": "Kata Sandi", - "prefix": "Prefiks unik (kosongkan jika hanya ada satu ElkM1).", - "protocol": "Protokol", - "temperature_unit": "Unit suhu yang digunakan ElkM1.", - "username": "Nama Pengguna" + "device": "Perangkat" }, "description": "Pilih sistem yang ditemukan atau 'Entri Manual' jika tidak ada perangkat yang ditemukan.", "title": "Hubungkan ke Kontrol Elk-M1" diff --git a/homeassistant/components/elkm1/translations/it.json b/homeassistant/components/elkm1/translations/it.json index fa05ab5d436..9284d2a495c 100644 --- a/homeassistant/components/elkm1/translations/it.json +++ b/homeassistant/components/elkm1/translations/it.json @@ -4,7 +4,9 @@ "address_already_configured": "Un ElkM1 con questo indirizzo \u00e8 gi\u00e0 configurato", "already_configured": "Un ElkM1 con questo prefisso \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", - "cannot_connect": "Impossibile connettersi" + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" }, "error": { "cannot_connect": "Impossibile connettersi", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "L'indirizzo IP o il dominio o la porta seriale se ci si connette tramite seriale.", - "device": "Dispositivo", - "password": "Password", - "prefix": "Un prefisso univoco (lascia vuoto se disponi di un solo ElkM1).", - "protocol": "Protocollo", - "temperature_unit": "L'unit\u00e0 di temperatura utilizzata da ElkM1.", - "username": "Nome utente" + "device": "Dispositivo" }, "description": "Scegli un sistema rilevato o \"Inserimento manuale\" se non sono stati rilevati dispositivi.", "title": "Connettiti al controllo Elk-M1" diff --git a/homeassistant/components/elkm1/translations/ja.json b/homeassistant/components/elkm1/translations/ja.json index f280d6d6276..3d86e5f673d 100644 --- a/homeassistant/components/elkm1/translations/ja.json +++ b/homeassistant/components/elkm1/translations/ja.json @@ -4,7 +4,9 @@ "address_already_configured": "\u3053\u306e\u30a2\u30c9\u30ec\u30b9\u306eElkM1\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "already_configured": "\u3053\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u6301\u3064ElkM1\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "IP\u30a2\u30c9\u30ec\u30b9\u307e\u305f\u306f\u30c9\u30e1\u30a4\u30f3\u3001\u30b7\u30ea\u30a2\u30eb\u3067\u63a5\u7d9a\u3059\u308b\u5834\u5408\u306f\u30b7\u30ea\u30a2\u30eb\u30dd\u30fc\u30c8\u3002", - "device": "\u30c7\u30d0\u30a4\u30b9", - "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", - "prefix": "\u30e6\u30cb\u30fc\u30af(\u4e00\u610f)\u306a\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9(ElkM1\u304c1\u3064\u3057\u304b\u306a\u3044\u5834\u5408\u306f\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059)", - "protocol": "\u30d7\u30ed\u30c8\u30b3\u30eb", - "temperature_unit": "ElkM1\u304c\u4f7f\u7528\u3059\u308b\u6e29\u5ea6\u5358\u4f4d\u3002", - "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + "device": "\u30c7\u30d0\u30a4\u30b9" }, "description": "\u30a2\u30c9\u30ec\u30b9\u6587\u5b57\u5217\u306f\u3001 '\u30bb\u30ad\u30e5\u30a2 '\u304a\u3088\u3073 '\u975e\u30bb\u30ad\u30e5\u30a2 '\u306e\u5834\u5408\u306f\u3001'address[:port]'\u306e\u5f62\u5f0f\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u4f8b: '192.168.1.1'\u3002\u30dd\u30fc\u30c8\u306f\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u306f'\u975e\u30bb\u30ad\u30e5\u30a2'\u306e\u5834\u5408\u306f\u30012101 \u3067'\u30bb\u30ad\u30e5\u30a2'\u306e\u5834\u5408\u306f\u30012601 \u3067\u3059\u3002\u30b7\u30ea\u30a2\u30eb \u30d7\u30ed\u30c8\u30b3\u30eb\u306e\u5834\u5408\u3001\u30a2\u30c9\u30ec\u30b9\u306f\u3001'tty[:baud]' \u306e\u5f62\u5f0f\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002\u4f8b: '/dev/ttyS1'\u3002\u30dc\u30fc\u306f\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u306f115200\u3067\u3059\u3002", "title": "Elk-M1 Control\u306b\u63a5\u7d9a" diff --git a/homeassistant/components/elkm1/translations/ko.json b/homeassistant/components/elkm1/translations/ko.json index 507f741676a..6ffa3679946 100644 --- a/homeassistant/components/elkm1/translations/ko.json +++ b/homeassistant/components/elkm1/translations/ko.json @@ -11,14 +11,6 @@ }, "step": { "user": { - "data": { - "address": "\uc2dc\ub9ac\uc5bc\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub294 \uacbd\uc6b0\uc758 IP \uc8fc\uc18c\ub098 \ub3c4\uba54\uc778 \ub610\ub294 \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8.", - "password": "\ube44\ubc00\ubc88\ud638", - "prefix": "\uace0\uc720\ud55c \uc811\ub450\uc0ac (ElkM1 \uc774 \ud558\ub098\ub9cc \uc788\uc73c\uba74 \ube44\uc6cc\ub450\uc138\uc694).", - "protocol": "\ud504\ub85c\ud1a0\ucf5c", - "temperature_unit": "ElkM1 \uc774 \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704.", - "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - }, "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548'\uc5d0 \ub300\ud574 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \uc804\uc1a1 \uc18d\ub3c4\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", "title": "Elk-M1 \uc81c\uc5b4\uc5d0 \uc5f0\uacb0\ud558\uae30" } diff --git a/homeassistant/components/elkm1/translations/lb.json b/homeassistant/components/elkm1/translations/lb.json index 084b4a1347f..14d2a82b97e 100644 --- a/homeassistant/components/elkm1/translations/lb.json +++ b/homeassistant/components/elkm1/translations/lb.json @@ -11,14 +11,6 @@ }, "step": { "user": { - "data": { - "address": "IP Adress oder Domain oder Serielle Port falls d'Verbindung seriell ass.", - "password": "Passwuert", - "prefix": "Een eenzegaartege Pr\u00e4fix (eidel lossen wann et n\u00ebmmen 1 ElkM1 g\u00ebtt)", - "protocol": "Protokoll", - "temperature_unit": "Temperatur Eenheet d\u00e9i den ElkM1 benotzt.", - "username": "Benotzernumm" - }, "description": "D'Adress muss an der Form 'adress[:port]' fir 'ges\u00e9chert' an 'onges\u00e9chert' sinn. Beispill: '192.168.1.1'. De Port os optionell an ass standardm\u00e9isseg op 2101 fir 'onges\u00e9chert' an op 2601 fir 'ges\u00e9chert' d\u00e9fin\u00e9iert. Fir de serielle Protokoll, muss d'Adress an der Form 'tty[:baud]' sinn. Beispill: '/dev/ttyS1'. Baud Rate ass optionell an ass standardm\u00e9isseg op 115200 d\u00e9fin\u00e9iert.", "title": "Mat Elk-M1 Control verbannen" } diff --git a/homeassistant/components/elkm1/translations/nl.json b/homeassistant/components/elkm1/translations/nl.json index c3efd45dbc0..c27293ce050 100644 --- a/homeassistant/components/elkm1/translations/nl.json +++ b/homeassistant/components/elkm1/translations/nl.json @@ -4,7 +4,9 @@ "address_already_configured": "Een ElkM1 met dit adres is al geconfigureerd", "already_configured": "Een ElkM1 met dit voorvoegsel is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", - "cannot_connect": "Kan geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Het IP-adres of domein of seri\u00eble poort bij verbinding via serieel.", - "device": "Apparaat", - "password": "Wachtwoord", - "prefix": "Een uniek voorvoegsel (laat dit leeg als u maar \u00e9\u00e9n ElkM1 heeft).", - "protocol": "Protocol", - "temperature_unit": "De temperatuureenheid die ElkM1 gebruikt.", - "username": "Gebruikersnaam" + "device": "Apparaat" }, "description": "Kies een ontdekt systeem of 'Handmatige invoer' als er geen apparaten zijn ontdekt.", "title": "Maak verbinding met Elk-M1 Control" diff --git a/homeassistant/components/elkm1/translations/no.json b/homeassistant/components/elkm1/translations/no.json index ced77ff0d04..221b9206c81 100644 --- a/homeassistant/components/elkm1/translations/no.json +++ b/homeassistant/components/elkm1/translations/no.json @@ -4,7 +4,9 @@ "address_already_configured": "En ElkM1 med denne adressen er allerede konfigurert", "already_configured": "En ElkM1 med dette prefikset er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", - "cannot_connect": "Tilkobling mislyktes" + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" }, "error": { "cannot_connect": "Tilkobling mislyktes", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "IP-adressen eller domenet eller seriell port hvis du kobler til via seriell.", - "device": "Enhet", - "password": "Passord", - "prefix": "Et unikt prefiks (la v\u00e6re tomt hvis du bare har en ElkM1).", - "protocol": "Protokoll", - "temperature_unit": "Temperaturenheten ElkM1 bruker.", - "username": "Brukernavn" + "device": "Enhet" }, "description": "Velg et oppdaget system eller \"Manuell oppf\u00f8ring\" hvis ingen enheter har blitt oppdaget.", "title": "Koble til Elk-M1-kontroll" diff --git a/homeassistant/components/elkm1/translations/pl.json b/homeassistant/components/elkm1/translations/pl.json index 62900716f62..3b27226438f 100644 --- a/homeassistant/components/elkm1/translations/pl.json +++ b/homeassistant/components/elkm1/translations/pl.json @@ -4,7 +4,9 @@ "address_already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane z tym adresem", "already_configured": "ElkM1 z tym prefiksem jest ju\u017c skonfigurowany", "already_in_progress": "Konfiguracja jest ju\u017c w toku", - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Adres IP, domena lub port szeregowy w przypadku po\u0142\u0105czenia szeregowego.", - "device": "Urz\u0105dzenie", - "password": "Has\u0142o", - "prefix": "Unikatowy prefiks (pozostaw pusty, je\u015bli masz tylko jeden ElkM1).", - "protocol": "Protok\u00f3\u0142", - "temperature_unit": "Jednostka temperatury u\u017cywanej przez ElkM1.", - "username": "Nazwa u\u017cytkownika" + "device": "Urz\u0105dzenie" }, "description": "Wybierz wykryty system lub \u201eWpis r\u0119czny\u201d, je\u015bli nie wykryto \u017cadnych urz\u0105dze\u0144.", "title": "Pod\u0142\u0105czenie do sterownika Elk-M1" diff --git a/homeassistant/components/elkm1/translations/pt-BR.json b/homeassistant/components/elkm1/translations/pt-BR.json index 7cb9a5f101a..6fb03ab66e8 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -4,7 +4,9 @@ "address_already_configured": "Um ElkM1 com este endere\u00e7o j\u00e1 est\u00e1 configurado", "already_configured": "A conta j\u00e1 foi configurada", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" }, "error": { "cannot_connect": "Falha ao conectar", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "O endere\u00e7o IP ou dom\u00ednio ou porta serial se estiver conectando via serial.", - "device": "Dispositivo", - "password": "Senha", - "prefix": "Um prefixo exclusivo (deixe em branco se voc\u00ea tiver apenas um ElkM1).", - "protocol": "Protocolo", - "temperature_unit": "A unidade de temperatura que ElkM1 usa.", - "username": "Usu\u00e1rio" + "device": "Dispositivo" }, "description": "Escolha um sistema descoberto ou 'Entrada Manual' se nenhum dispositivo foi descoberto.", "title": "Conecte ao controle Elk-M1" diff --git a/homeassistant/components/elkm1/translations/pt.json b/homeassistant/components/elkm1/translations/pt.json index 48d278ac354..2e669c21f1e 100644 --- a/homeassistant/components/elkm1/translations/pt.json +++ b/homeassistant/components/elkm1/translations/pt.json @@ -4,15 +4,6 @@ "cannot_connect": "Falha na liga\u00e7\u00e3o", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" - }, - "step": { - "user": { - "data": { - "password": "Palavra-passe (segura apenas)", - "protocol": "Protocolo", - "username": "Nome de utilizador (apenas seguro)." - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/ru.json b/homeassistant/components/elkm1/translations/ru.json index 1b4bf7250d7..624166fea48 100644 --- a/homeassistant/components/elkm1/translations/ru.json +++ b/homeassistant/components/elkm1/translations/ru.json @@ -4,7 +4,9 @@ "address_already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u044d\u0442\u0438\u043c \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u044d\u0442\u0438\u043c \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "IP-\u0430\u0434\u0440\u0435\u0441, \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442", - "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "prefix": "\u0423\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d ElkM1)", - "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", - "temperature_unit": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", - "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438\u043b\u0438 'Manual Entry', \u0435\u0441\u043b\u0438 \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0431\u044b\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b.", "title": "Elk-M1 Control" diff --git a/homeassistant/components/elkm1/translations/sl.json b/homeassistant/components/elkm1/translations/sl.json index a815011988e..50988abb1d3 100644 --- a/homeassistant/components/elkm1/translations/sl.json +++ b/homeassistant/components/elkm1/translations/sl.json @@ -11,14 +11,6 @@ }, "step": { "user": { - "data": { - "address": "IP naslov, domena ali serijska vrata, \u010de se povezujete prek serijske povezave.", - "password": "Geslo (samo varno).", - "prefix": "Edinstvena predpona (pustite prazno, \u010de imate samo en ElkM1).", - "protocol": "Protokol", - "temperature_unit": "Temperaturna enota, ki jo uporablja ElkM1.", - "username": "Uporabni\u0161ko ime (samo varno)." - }, "description": "Naslov mora biti v obliki \"naslov[:port]\" za \"varno\" in \"ne-varno'. Primer: '192.168.1.1'. Vrata so neobvezna in so privzeto nastavljena na 2101 za \"non-secure\" in 2601 za 'varno'. Za serijski protokol, mora biti naslov v obliki \" tty[:baud]'. Primer: '/dev/ttyS1'. Baud je neobvezen in privzeto nastavljen na 115200.", "title": "Pove\u017eite se z Elk-M1 Control" } diff --git a/homeassistant/components/elkm1/translations/sv.json b/homeassistant/components/elkm1/translations/sv.json index 23a7d475a6f..19d9bb17e4b 100644 --- a/homeassistant/components/elkm1/translations/sv.json +++ b/homeassistant/components/elkm1/translations/sv.json @@ -4,13 +4,6 @@ "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", "invalid_auth": "Ogiltig autentisering", "unknown": "Ov\u00e4ntat fel" - }, - "step": { - "user": { - "data": { - "protocol": "Protokoll" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/tr.json b/homeassistant/components/elkm1/translations/tr.json index e8f147c8d57..d1c865bffd5 100644 --- a/homeassistant/components/elkm1/translations/tr.json +++ b/homeassistant/components/elkm1/translations/tr.json @@ -4,7 +4,9 @@ "address_already_configured": "Bu adrese sahip bir ElkM1 zaten yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r", "already_configured": "Bu \u00f6nek ile bir ElkM1 zaten yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", - "cannot_connect": "Ba\u011flanma hatas\u0131" + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "Seri yoluyla ba\u011flan\u0131l\u0131yorsa IP adresi veya etki alan\u0131 veya seri ba\u011flant\u0131 noktas\u0131.", - "device": "Cihaz", - "password": "Parola", - "prefix": "Benzersiz bir \u00f6nek (yaln\u0131zca bir ElkM1'iniz varsa bo\u015f b\u0131rak\u0131n).", - "protocol": "Protokol", - "temperature_unit": "ElkM1'in kulland\u0131\u011f\u0131 s\u0131cakl\u0131k birimi.", - "username": "Kullan\u0131c\u0131 Ad\u0131" + "device": "Cihaz" }, "description": "Ke\u015ffedilen bir sistem veya ke\u015ffedilmemi\u015fse 'Manuel Giri\u015f' se\u00e7in.", "title": "Elk-M1 Kontrol\u00fcne Ba\u011flan\u0131n" diff --git a/homeassistant/components/elkm1/translations/uk.json b/homeassistant/components/elkm1/translations/uk.json index a8e711a4590..d794c6ecf69 100644 --- a/homeassistant/components/elkm1/translations/uk.json +++ b/homeassistant/components/elkm1/translations/uk.json @@ -11,14 +11,6 @@ }, "step": { "user": { - "data": { - "address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430, \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e \u043f\u043e\u0441\u043b\u0456\u0434\u043e\u0432\u043d\u0438\u0439 \u043f\u043e\u0440\u0442", - "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "prefix": "\u0423\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0434\u0438\u043d ElkM1)", - "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", - "temperature_unit": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438", - "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - }, "description": "\u0420\u044f\u0434\u043e\u043a \u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'addres[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0456\u0432 'secure' \u0456 'non-secure' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '192.168.1.1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 2101 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'non-secure' \u0456 2601 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'secure'. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'serial' \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 115200.", "title": "Elk-M1 Control" } diff --git a/homeassistant/components/elkm1/translations/zh-Hant.json b/homeassistant/components/elkm1/translations/zh-Hant.json index 543f93626ce..9405180fe0d 100644 --- a/homeassistant/components/elkm1/translations/zh-Hant.json +++ b/homeassistant/components/elkm1/translations/zh-Hant.json @@ -4,7 +4,9 @@ "address_already_configured": "\u4f7f\u7528\u6b64\u4f4d\u5740\u7684\u4e00\u7d44 ElkM1 \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_configured": "\u4f7f\u7528\u6b64 Prefix \u7684\u4e00\u7d44 ElkM1 \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", - "cannot_connect": "\u9023\u7dda\u5931\u6557" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", @@ -37,13 +39,7 @@ }, "user": { "data": { - "address": "IP \u6216\u7db2\u57df\u540d\u7a31\u3001\u5e8f\u5217\u57e0\uff08\u5047\u5982\u900f\u904e\u5e8f\u5217\u9023\u7dda\uff09\u3002", - "device": "\u88dd\u7f6e", - "password": "\u5bc6\u78bc", - "prefix": "\u7368\u4e00\u7684 Prefix\uff08\u5047\u5982\u50c5\u6709\u4e00\u7d44 ElkM1 \u5247\u4fdd\u7559\u7a7a\u767d\uff09\u3002", - "protocol": "\u901a\u8a0a\u5354\u5b9a", - "temperature_unit": "ElkM1 \u4f7f\u7528\u6eab\u5ea6\u55ae\u4f4d\u3002", - "username": "\u4f7f\u7528\u8005\u540d\u7a31" + "device": "\u88dd\u7f6e" }, "description": "\u9078\u64c7\u6240\u767c\u73fe\u5230\u7684\u7cfb\u7d71\uff0c\u6216\u5047\u5982\u6c92\u627e\u5230\u7684\u8a71\u9032\u884c\u624b\u52d5\u8f38\u5165\u3002", "title": "\u9023\u7dda\u81f3 Elk-M1 Control" diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py index 6fbef891bd1..0278028c458 100644 --- a/homeassistant/components/emby/media_player.py +++ b/homeassistant/components/emby/media_player.py @@ -6,18 +6,16 @@ import logging from pyemby import EmbyServer import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_STOP, ) from homeassistant.const import ( CONF_API_KEY, @@ -49,12 +47,12 @@ DEFAULT_SSL_PORT = 8920 DEFAULT_SSL = False SUPPORT_EMBY = ( - SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_STOP - | SUPPORT_SEEK - | SUPPORT_PLAY + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.PLAY ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 8879743446e..af684067ec8 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -163,12 +163,30 @@ class EmonCmsSensor(SensorEntity): self._sensorid = sensorid self._elem = elem - if unit_of_measurement == "kWh": + if unit_of_measurement in ("kWh", "Wh"): self._attr_device_class = SensorDeviceClass.ENERGY self._attr_state_class = SensorStateClass.TOTAL_INCREASING elif unit_of_measurement == "W": self._attr_device_class = SensorDeviceClass.POWER self._attr_state_class = SensorStateClass.MEASUREMENT + elif unit_of_measurement == "V": + self._attr_device_class = SensorDeviceClass.VOLTAGE + self._attr_state_class = SensorStateClass.MEASUREMENT + elif unit_of_measurement == "A": + self._attr_device_class = SensorDeviceClass.CURRENT + self._attr_state_class = SensorStateClass.MEASUREMENT + elif unit_of_measurement == "VA": + self._attr_device_class = SensorDeviceClass.APPARENT_POWER + self._attr_state_class = SensorStateClass.MEASUREMENT + elif unit_of_measurement in ("°C", "°F", "K"): + self._attr_device_class = SensorDeviceClass.TEMPERATURE + self._attr_state_class = SensorStateClass.MEASUREMENT + elif unit_of_measurement == "Hz": + self._attr_device_class = SensorDeviceClass.FREQUENCY + self._attr_state_class = SensorStateClass.MEASUREMENT + elif unit_of_measurement == "hPa": + self._attr_device_class = SensorDeviceClass.PRESSURE + self._attr_state_class = SensorStateClass.MEASUREMENT if self._value_template is not None: self._state = self._value_template.render_with_possible_json_value( diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 6f8cf77b13d..b4f926afd31 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -19,14 +19,14 @@ from homeassistant.components import ( ) from homeassistant.components.climate.const import ( SERVICE_SET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, ) from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_POSITION, - SUPPORT_SET_POSITION, + CoverEntityFeature, ) -from homeassistant.components.fan import ATTR_PERCENTAGE, SUPPORT_SET_SPEED +from homeassistant.components.fan import ATTR_PERCENTAGE, FanEntityFeature from homeassistant.components.http import HomeAssistantView from homeassistant.components.humidifier.const import ( ATTR_HUMIDITY, @@ -38,11 +38,11 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_XY_COLOR, - SUPPORT_TRANSITION, + LightEntityFeature, ) from homeassistant.components.media_player.const import ( ATTR_MEDIA_VOLUME_LEVEL, - SUPPORT_VOLUME_SET, + MediaPlayerEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -461,7 +461,7 @@ class HueOneLightChangeView(HomeAssistantView): data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP] if ( - entity_features & SUPPORT_TRANSITION + entity_features & LightEntityFeature.TRANSITION and parsed[STATE_TRANSITION] is not None ): data[ATTR_TRANSITION] = parsed[STATE_TRANSITION] / 10 @@ -482,7 +482,7 @@ class HueOneLightChangeView(HomeAssistantView): service = None if ( - entity_features & SUPPORT_TARGET_TEMPERATURE + entity_features & ClimateEntityFeature.TARGET_TEMPERATURE and parsed[STATE_BRIGHTNESS] is not None ): domain = entity.domain @@ -500,7 +500,7 @@ class HueOneLightChangeView(HomeAssistantView): # If the requested entity is a media player, convert to volume elif entity.domain == media_player.DOMAIN: if ( - entity_features & SUPPORT_VOLUME_SET + entity_features & MediaPlayerEntityFeature.VOLUME_SET and parsed[STATE_BRIGHTNESS] is not None ): turn_on_needed = True @@ -518,7 +518,7 @@ class HueOneLightChangeView(HomeAssistantView): service = SERVICE_CLOSE_COVER if ( - entity_features & SUPPORT_SET_POSITION + entity_features & CoverEntityFeature.SET_POSITION and parsed[STATE_BRIGHTNESS] is not None ): domain = entity.domain @@ -528,7 +528,7 @@ class HueOneLightChangeView(HomeAssistantView): # If the requested entity is a fan, convert to speed elif ( entity.domain == fan.DOMAIN - and entity_features & SUPPORT_SET_SPEED + and entity_features & FanEntityFeature.SET_SPEED and parsed[STATE_BRIGHTNESS] is not None ): domain = entity.domain @@ -766,10 +766,10 @@ def entity_to_json(config, entity): } ) elif entity_features & ( - SUPPORT_SET_POSITION - | SUPPORT_SET_SPEED - | SUPPORT_VOLUME_SET - | SUPPORT_TARGET_TEMPERATURE + CoverEntityFeature.SET_POSITION + | FanEntityFeature.SET_SPEED + | MediaPlayerEntityFeature.VOLUME_SET + | ClimateEntityFeature.TARGET_TEMPERATURE ) or light.brightness_supported(color_modes): # Dimmable light (Zigbee Device ID: 0x0100) # Supports groups, scenes, on/off and dimming diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index 8ff7eb85b39..797b22c22f7 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -127,7 +127,7 @@ class UPNPResponderProtocol: _LOGGER.debug("UPNP Responder responding with: %s", response) self.transport.sendto(response, addr) - def error_received(self, exc): # pylint: disable=no-self-use + def error_received(self, exc): """Log UPNP errors.""" _LOGGER.error("UPNP Error received: %s", exc) diff --git a/homeassistant/components/emulated_roku/translations/hu.json b/homeassistant/components/emulated_roku/translations/hu.json index 53b66f6db19..74cbdbcec79 100644 --- a/homeassistant/components/emulated_roku/translations/hu.json +++ b/homeassistant/components/emulated_roku/translations/hu.json @@ -10,7 +10,7 @@ "advertise_port": "Port k\u00f6zl\u00e9se", "host_ip": "IP c\u00edm", "listen_port": "Port", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "upnp_bind_multicast": "K\u00f6t\u00f6tt multicast (igaz/hamis)" }, "title": "A kiszolg\u00e1l\u00f3 szerver konfigur\u00e1l\u00e1sa" diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index f8591e5c23f..4002f6c416d 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -29,7 +29,7 @@ from homeassistant.core import ( split_entity_id, valid_entity_id, ) -from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -210,7 +210,7 @@ class EnergyCostSensor(SensorEntity): utility. """ - _attr_entity_category = EntityCategory.SYSTEM + _attr_entity_registry_visible_default = False _wrong_state_class_reported = False _wrong_unit_reported = False @@ -416,3 +416,16 @@ class EnergyCostSensor(SensorEntity): def native_unit_of_measurement(self) -> str | None: """Return the units of measurement.""" return self.hass.config.currency + + @property + def unique_id(self) -> str | None: + """Return the unique ID of the sensor.""" + entity_registry = er.async_get(self.hass) + if registry_entry := entity_registry.async_get( + self._config[self._adapter.entity_energy_key] + ): + prefix = registry_entry.id + else: + prefix = self._config[self._adapter.entity_energy_key] + + return f"{prefix}_{self._adapter.source_type}_{self._adapter.entity_id_suffix}" diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 546aa1db099..c240d882f8c 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -4,20 +4,11 @@ from __future__ import annotations from openwebif.api import CreateDevice import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_TVSHOW, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_TVSHOW from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -55,19 +46,6 @@ DEFAULT_DEEP_STANDBY = False DEFAULT_MAC_ADDRESS = "" DEFAULT_SOURCE_BOUQUET = "" -SUPPORTED_ENIGMA2 = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_OFF - | SUPPORT_NEXT_TRACK - | SUPPORT_STOP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_VOLUME_STEP - | SUPPORT_TURN_ON - | SUPPORT_PAUSE - | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -126,6 +104,19 @@ def setup_platform( class Enigma2Device(MediaPlayerEntity): """Representation of an Enigma2 box.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, name, device): """Initialize the Enigma2 device.""" self._name = name @@ -153,11 +144,6 @@ class Enigma2Device(MediaPlayerEntity): """Return True if the device is available.""" return not self.e2_box.is_offline - @property - def supported_features(self): - """Flag of media commands that are supported.""" - return SUPPORTED_ENIGMA2 - def turn_off(self): """Turn off media player.""" self.e2_box.turn_off() diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py index 92887be9b45..c7bf076c738 100644 --- a/homeassistant/components/enocean/light.py +++ b/homeassistant/components/enocean/light.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_ID, CONF_NAME @@ -22,7 +22,6 @@ from .device import EnOceanEntity CONF_SENDER_ID = "sender_id" DEFAULT_NAME = "EnOcean Light" -SUPPORT_ENOCEAN = SUPPORT_BRIGHTNESS PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -50,6 +49,9 @@ def setup_platform( class EnOceanLight(EnOceanEntity, LightEntity): """Representation of an EnOcean light source.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, sender_id, dev_id, dev_name): """Initialize the EnOcean light source.""" super().__init__(dev_id, dev_name) @@ -76,11 +78,6 @@ class EnOceanLight(EnOceanEntity, LightEntity): """If light is on.""" return self._on_state - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_ENOCEAN - def turn_on(self, **kwargs): """Turn the light source on or sets a specific dimmer value.""" if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None: diff --git a/homeassistant/components/enphase_envoy/translations/en.json b/homeassistant/components/enphase_envoy/translations/en.json index ff600fea454..5d4617ed9fa 100644 --- a/homeassistant/components/enphase_envoy/translations/en.json +++ b/homeassistant/components/enphase_envoy/translations/en.json @@ -2,8 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", - "reauth_successful": "Re-authentication was successful", - "not_ipv4_address": "Only IPv4 addresess are supported" + "reauth_successful": "Re-authentication was successful" }, "error": { "cannot_connect": "Failed to connect", diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index e09ae99e006..e415bab977b 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -1,14 +1,24 @@ """Support for the Environment Canada radar imagery.""" from __future__ import annotations +import voluptuous as vol + from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_platform import ( + AddEntitiesCallback, + async_get_current_platform, +) from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ATTR_OBSERVATION_TIME, DOMAIN +SERVICE_SET_RADAR_TYPE = "set_radar_type" +SET_RADAR_TYPE_SCHEMA = { + vol.Required("radar_type"): vol.In(["Auto", "Rain", "Snow"]), +} + async def async_setup_entry( hass: HomeAssistant, @@ -19,6 +29,13 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][config_entry.entry_id]["radar_coordinator"] async_add_entities([ECCamera(coordinator)]) + platform = async_get_current_platform() + platform.async_register_entity_service( + SERVICE_SET_RADAR_TYPE, + SET_RADAR_TYPE_SCHEMA, + "async_set_radar_type", + ) + class ECCamera(CoordinatorEntity, Camera): """Implementation of an Environment Canada radar camera.""" @@ -35,19 +52,17 @@ class ECCamera(CoordinatorEntity, Camera): self._attr_entity_registry_enabled_default = False self.content_type = "image/gif" - self.image = None - self.observation_time = None def camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return bytes of camera image.""" - if not hasattr(self.radar_object, "timestamp"): - return None - self.observation_time = self.radar_object.timestamp + self._attr_extra_state_attributes = { + ATTR_OBSERVATION_TIME: self.radar_object.timestamp, + } return self.radar_object.image - @property - def extra_state_attributes(self): - """Return the state attributes of the device.""" - return {ATTR_OBSERVATION_TIME: self.observation_time} + async def async_set_radar_type(self, radar_type: str): + """Set the type of radar to retrieve.""" + self.radar_object.precip_type = radar_type.lower() + await self.radar_object.update() diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json index a0399629030..83c7ca455cb 100644 --- a/homeassistant/components/environment_canada/manifest.json +++ b/homeassistant/components/environment_canada/manifest.json @@ -2,7 +2,7 @@ "domain": "environment_canada", "name": "Environment Canada", "documentation": "https://www.home-assistant.io/integrations/environment_canada", - "requirements": ["env_canada==0.5.21"], + "requirements": ["env_canada==0.5.22"], "codeowners": ["@gwww", "@michaeldavie"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/components/environment_canada/services.yaml b/homeassistant/components/environment_canada/services.yaml new file mode 100644 index 00000000000..09f95f16a44 --- /dev/null +++ b/homeassistant/components/environment_canada/services.yaml @@ -0,0 +1,19 @@ +set_radar_type: + name: Set radar type + description: Set the type of radar image to retrieve. + target: + entity: + integration: environment_canada + domain: camera + fields: + radar_type: + name: Radar type + description: The type of radar image to display. + required: true + example: Snow + selector: + select: + options: + - "Auto" + - "Rain" + - "Snow" diff --git a/homeassistant/components/environment_canada/translations/de.json b/homeassistant/components/environment_canada/translations/de.json index c351683007f..6c9be14be27 100644 --- a/homeassistant/components/environment_canada/translations/de.json +++ b/homeassistant/components/environment_canada/translations/de.json @@ -15,7 +15,7 @@ "longitude": "L\u00e4ngengrad", "station": "ID der Wetterstation" }, - "description": "Es muss entweder eine Stations-ID oder der Breitengrad/L\u00e4ngengrad angegeben werden. Als Standardwerte f\u00fcr Breitengrad/L\u00e4ngengrad werden die in Ihrer Home Assistant-Installation konfigurierten Werte verwendet. Bei Angabe von Koordinaten wird die den Koordinaten am n\u00e4chsten gelegene Wetterstation verwendet. Wenn ein Stationscode verwendet wird, muss er dem Format entsprechen: PP/Code, wobei PP f\u00fcr die zweistellige Provinz und Code f\u00fcr die Stationskennung steht. Die Liste der Stations-IDs findest du hier: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. Die Wetterinformationen k\u00f6nnen entweder in Englisch oder Franz\u00f6sisch abgerufen werden.", + "description": "Es muss entweder eine Stations-ID oder der Breitengrad/L\u00e4ngengrad angegeben werden. Als Standardwerte f\u00fcr Breitengrad/L\u00e4ngengrad werden die in deiner Home Assistant-Installation konfigurierten Werte verwendet. Bei Angabe von Koordinaten wird die den Koordinaten am n\u00e4chsten gelegene Wetterstation verwendet. Wenn ein Stationscode verwendet wird, muss er dem Format entsprechen: PP/Code, wobei PP f\u00fcr die zweistellige Provinz und Code f\u00fcr die Stationskennung steht. Die Liste der Stations-IDs findest du hier: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. Die Wetterinformationen k\u00f6nnen entweder in Englisch oder Franz\u00f6sisch abgerufen werden.", "title": "Standort Kanada: Wetterstandort und Sprache" } } diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index 1b81750fb47..b79323b0462 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -212,7 +212,7 @@ def get_forecast(ec_data, hourly): for hour in ec_data.hourly_forecasts: forecast_array.append( { - ATTR_FORECAST_TIME: hour["period"], + ATTR_FORECAST_TIME: hour["period"].isoformat(), ATTR_FORECAST_TEMP: int(hour["temperature"]), ATTR_FORECAST_CONDITION: icon_code_to_condition( int(hour["icon_code"]) diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index f8341a82d82..3dfa1e0762d 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -6,14 +6,9 @@ import logging import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - FORMAT_NUMBER, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_TRIGGER, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -108,6 +103,13 @@ async def async_setup_platform( class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity): """Representation of an Envisalink-based alarm panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.TRIGGER + ) + def __init__( self, hass, partition_number, alarm_name, code, panic_type, info, controller ): @@ -143,7 +145,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity): """Regex for code format or None if no code is required.""" if self._code: return None - return FORMAT_NUMBER + return CodeFormat.NUMBER @property def state(self): @@ -166,16 +168,6 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity): state = STATE_ALARM_DISARMED return state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return ( - SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_TRIGGER - ) - async def async_alarm_disarm(self, code=None): """Send disarm command.""" if code: diff --git a/homeassistant/components/ephember/climate.py b/homeassistant/components/ephember/climate.py index 7a166511eb3..6bc95818329 100644 --- a/homeassistant/components/ephember/climate.py +++ b/homeassistant/components/ephember/climate.py @@ -19,13 +19,9 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_AUX_HEAT, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -43,16 +39,16 @@ _LOGGER = logging.getLogger(__name__) # Return cached results if last scan was less then this time ago SCAN_INTERVAL = timedelta(seconds=120) -OPERATION_LIST = [HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] +OPERATION_LIST = [HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.OFF] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} ) EPH_TO_HA_STATE = { - "AUTO": HVAC_MODE_HEAT_COOL, - "ON": HVAC_MODE_HEAT, - "OFF": HVAC_MODE_OFF, + "AUTO": HVACMode.HEAT_COOL, + "ON": HVACMode.HEAT, + "OFF": HVACMode.OFF, } HA_STATE_TO_EPH = {value: key for key, value in EPH_TO_HA_STATE.items()} @@ -94,9 +90,9 @@ class EphEmberThermostat(ClimateEntity): def supported_features(self): """Return the list of supported features.""" if self._hot_water: - return SUPPORT_AUX_HEAT + return ClimateEntityFeature.AUX_HEAT - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT + return ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.AUX_HEAT @property def name(self): @@ -130,9 +126,9 @@ class EphEmberThermostat(ClimateEntity): def hvac_action(self): """Return current HVAC action.""" if zone_is_active(self._zone): - return CURRENT_HVAC_HEAT + return HVACAction.HEATING - return CURRENT_HVAC_IDLE + return HVACAction.IDLE @property def hvac_mode(self): @@ -214,4 +210,4 @@ class EphEmberThermostat(ClimateEntity): @staticmethod def map_mode_eph_hass(operation_mode): """Map from eph mode to Home Assistant mode.""" - return EPH_TO_HA_STATE.get(operation_mode.name, HVAC_MODE_HEAT_COOL) + return EPH_TO_HA_STATE.get(operation_mode.name, HVACMode.HEAT_COOL) diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index be0e58c3795..f72b0f69d69 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -28,15 +28,9 @@ from epson_projector.const import ( ) import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON @@ -51,16 +45,6 @@ from .const import ATTR_CMODE, DOMAIN, SERVICE_SELECT_CMODE _LOGGER = logging.getLogger(__name__) -SUPPORT_EPSON = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_STEP - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK -) - async def async_setup_entry( hass: HomeAssistant, @@ -89,6 +73,16 @@ async def async_setup_entry( class EpsonProjectorMediaPlayer(MediaPlayerEntity): """Representation of Epson Projector Device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + ) + def __init__(self, projector, name, unique_id, entry): """Initialize entity to control Epson projector.""" self._projector = projector @@ -178,11 +172,6 @@ class EpsonProjectorMediaPlayer(MediaPlayerEntity): """Return if projector is available.""" return self._available - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_EPSON - async def async_turn_on(self): """Turn on epson.""" if self._state == STATE_OFF: diff --git a/homeassistant/components/epson/translations/hu.json b/homeassistant/components/epson/translations/hu.json index e3aa507b7c1..ff56db57da9 100644 --- a/homeassistant/components/epson/translations/hu.json +++ b/homeassistant/components/epson/translations/hu.json @@ -8,7 +8,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" } } } diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index d514d54aa66..1f95f03bd17 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -9,14 +9,11 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -44,18 +41,18 @@ ATTR_STATE_LOW_BAT = "low_battery" ATTR_STATE_AWAY_END = "away_end" EQ_TO_HA_HVAC = { - eq3.Mode.Open: HVAC_MODE_HEAT, - eq3.Mode.Closed: HVAC_MODE_OFF, - eq3.Mode.Auto: HVAC_MODE_AUTO, - eq3.Mode.Manual: HVAC_MODE_HEAT, - eq3.Mode.Boost: HVAC_MODE_AUTO, - eq3.Mode.Away: HVAC_MODE_HEAT, + eq3.Mode.Open: HVACMode.HEAT, + eq3.Mode.Closed: HVACMode.OFF, + eq3.Mode.Auto: HVACMode.AUTO, + eq3.Mode.Manual: HVACMode.HEAT, + eq3.Mode.Boost: HVACMode.AUTO, + eq3.Mode.Away: HVACMode.HEAT, } HA_TO_EQ_HVAC = { - HVAC_MODE_HEAT: eq3.Mode.Manual, - HVAC_MODE_OFF: eq3.Mode.Closed, - HVAC_MODE_AUTO: eq3.Mode.Auto, + HVACMode.HEAT: eq3.Mode.Manual, + HVACMode.OFF: eq3.Mode.Closed, + HVACMode.AUTO: eq3.Mode.Auto, } EQ_TO_HA_PRESET = { @@ -83,8 +80,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_DEVICES): vol.Schema({cv.string: DEVICE_SCHEMA})} ) -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - def setup_platform( hass: HomeAssistant, @@ -105,6 +100,10 @@ def setup_platform( class EQ3BTSmartThermostat(ClimateEntity): """Representation of an eQ-3 Bluetooth Smart thermostat.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, _mac, _name): """Initialize the thermostat.""" # We want to avoid name clash with this module. @@ -112,11 +111,6 @@ class EQ3BTSmartThermostat(ClimateEntity): self._mac = _mac self._thermostat = eq3.Thermostat(_mac) - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - @property def available(self) -> bool: """Return if thermostat is available.""" @@ -157,7 +151,7 @@ class EQ3BTSmartThermostat(ClimateEntity): def hvac_mode(self): """Return the current operation mode.""" if self._thermostat.mode < 0: - return HVAC_MODE_OFF + return HVACMode.OFF return EQ_TO_HA_HVAC[self._thermostat.mode] @property @@ -196,7 +190,7 @@ class EQ3BTSmartThermostat(ClimateEntity): def preset_mode(self): """Return the current preset mode, e.g., home, away, temp. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ return EQ_TO_HA_PRESET.get(self._thermostat.mode) @@ -204,7 +198,7 @@ class EQ3BTSmartThermostat(ClimateEntity): def preset_modes(self): """Return a list of available preset modes. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ return list(HA_TO_EQ_PRESET) @@ -216,7 +210,7 @@ class EQ3BTSmartThermostat(ClimateEntity): def set_preset_mode(self, preset_mode): """Set new preset mode.""" if preset_mode == PRESET_NONE: - self.set_hvac_mode(HVAC_MODE_HEAT) + self.set_hvac_mode(HVACMode.HEAT) self._thermostat.mode = HA_TO_EQ_PRESET[preset_mode] def update(self): diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index 31d3e5f2320..3c0a80e780f 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -18,12 +18,6 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, FAN_AUTO, FAN_DIFFUSE, FAN_FOCUS, @@ -33,13 +27,6 @@ from homeassistant.components.climate.const import ( FAN_MIDDLE, FAN_OFF, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_ACTIVITY, PRESET_AWAY, PRESET_BOOST, @@ -48,15 +35,13 @@ from homeassistant.components.climate.const import ( PRESET_HOME, PRESET_NONE, PRESET_SLEEP, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -92,25 +77,25 @@ async def async_setup_entry( ) -_CLIMATE_MODES: EsphomeEnumMapper[ClimateMode, str] = EsphomeEnumMapper( +_CLIMATE_MODES: EsphomeEnumMapper[ClimateMode, HVACMode] = EsphomeEnumMapper( { - ClimateMode.OFF: HVAC_MODE_OFF, - ClimateMode.HEAT_COOL: HVAC_MODE_HEAT_COOL, - ClimateMode.COOL: HVAC_MODE_COOL, - ClimateMode.HEAT: HVAC_MODE_HEAT, - ClimateMode.FAN_ONLY: HVAC_MODE_FAN_ONLY, - ClimateMode.DRY: HVAC_MODE_DRY, - ClimateMode.AUTO: HVAC_MODE_AUTO, + ClimateMode.OFF: HVACMode.OFF, + ClimateMode.HEAT_COOL: HVACMode.HEAT_COOL, + ClimateMode.COOL: HVACMode.COOL, + ClimateMode.HEAT: HVACMode.HEAT, + ClimateMode.FAN_ONLY: HVACMode.FAN_ONLY, + ClimateMode.DRY: HVACMode.DRY, + ClimateMode.AUTO: HVACMode.AUTO, } ) -_CLIMATE_ACTIONS: EsphomeEnumMapper[ClimateAction, str] = EsphomeEnumMapper( +_CLIMATE_ACTIONS: EsphomeEnumMapper[ClimateAction, HVACAction] = EsphomeEnumMapper( { - ClimateAction.OFF: CURRENT_HVAC_OFF, - ClimateAction.COOLING: CURRENT_HVAC_COOL, - ClimateAction.HEATING: CURRENT_HVAC_HEAT, - ClimateAction.IDLE: CURRENT_HVAC_IDLE, - ClimateAction.DRYING: CURRENT_HVAC_DRY, - ClimateAction.FAN: CURRENT_HVAC_FAN, + ClimateAction.OFF: HVACAction.OFF, + ClimateAction.COOLING: HVACAction.COOLING, + ClimateAction.HEATING: HVACAction.HEATING, + ClimateAction.IDLE: HVACAction.IDLE, + ClimateAction.DRYING: HVACAction.DRYING, + ClimateAction.FAN: HVACAction.FAN, } ) _FAN_MODES: EsphomeEnumMapper[ClimateFanMode, str] = EsphomeEnumMapper( @@ -223,19 +208,19 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti """Return the list of supported features.""" features = 0 if self._static_info.supports_two_point_target_temperature: - features |= SUPPORT_TARGET_TEMPERATURE_RANGE + features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE else: - features |= SUPPORT_TARGET_TEMPERATURE + features |= ClimateEntityFeature.TARGET_TEMPERATURE if self.preset_modes: - features |= SUPPORT_PRESET_MODE + features |= ClimateEntityFeature.PRESET_MODE if self.fan_modes: - features |= SUPPORT_FAN_MODE + features |= ClimateEntityFeature.FAN_MODE if self.swing_modes: - features |= SUPPORT_SWING_MODE + features |= ClimateEntityFeature.SWING_MODE return features @esphome_state_property - def hvac_mode(self) -> str | None: # type: ignore[override] + def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" return _CLIMATE_MODES.from_esphome(self._state.mode) @@ -286,11 +271,13 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti """Return the highbound target temperature we try to reach.""" return self._state.target_temperature_high - async def async_set_temperature(self, **kwargs: float | str) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature (and operation mode if set).""" data: dict[str, Any] = {"key": self._static_info.key} if ATTR_HVAC_MODE in kwargs: - data["mode"] = _CLIMATE_MODES.from_hass(cast(str, kwargs[ATTR_HVAC_MODE])) + data["mode"] = _CLIMATE_MODES.from_hass( + cast(HVACMode, kwargs[ATTR_HVAC_MODE]) + ) if ATTR_TEMPERATURE in kwargs: data["target_temperature"] = kwargs[ATTR_TEMPERATURE] if ATTR_TARGET_TEMP_LOW in kwargs: @@ -299,7 +286,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti data["target_temperature_high"] = kwargs[ATTR_TARGET_TEMP_HIGH] await self._client.climate_command(**data) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" await self._client.climate_command( key=self._static_info.key, mode=_CLIMATE_MODES.from_hass(hvac_mode) diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 0e23050646f..4296c899253 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -9,14 +9,8 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DEVICE_CLASSES, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -50,11 +44,17 @@ class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity): @property def supported_features(self) -> int: """Flag supported features.""" - flags = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + flags = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP + ) if self._static_info.supports_position: - flags |= SUPPORT_SET_POSITION + flags |= CoverEntityFeature.SET_POSITION if self._static_info.supports_tilt: - flags |= SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION + flags |= ( + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION + ) return flags @property diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 91cb8bb6fdb..f2440465e77 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -9,10 +9,8 @@ from aioesphomeapi import FanDirection, FanInfo, FanSpeed, FanState from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -161,9 +159,9 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): """Flag supported features.""" flags = 0 if self._static_info.supports_oscillation: - flags |= SUPPORT_OSCILLATE + flags |= FanEntityFeature.OSCILLATE if self._static_info.supports_speed: - flags |= SUPPORT_SET_SPEED + flags |= FanEntityFeature.SET_SPEED if self._static_info.supports_direction: - flags |= SUPPORT_DIRECTION + flags |= FanEntityFeature.DIRECTION return flags diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index 44649e9e219..3eb45d63cac 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -15,20 +15,11 @@ from homeassistant.components.light import ( ATTR_RGBWW_COLOR, ATTR_TRANSITION, ATTR_WHITE, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_ONOFF, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_UNKNOWN, - COLOR_MODE_WHITE, FLASH_LONG, FLASH_SHORT, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -55,15 +46,15 @@ async def async_setup_entry( _COLOR_MODE_MAPPING = { - COLOR_MODE_ONOFF: [ + ColorMode.ONOFF: [ LightColorCapability.ON_OFF, ], - COLOR_MODE_BRIGHTNESS: [ + ColorMode.BRIGHTNESS: [ LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS, # for compatibility with older clients (2021.8.x) LightColorCapability.BRIGHTNESS, ], - COLOR_MODE_COLOR_TEMP: [ + ColorMode.COLOR_TEMP: [ LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | LightColorCapability.COLOR_TEMPERATURE, @@ -71,18 +62,18 @@ _COLOR_MODE_MAPPING = { | LightColorCapability.BRIGHTNESS | LightColorCapability.COLD_WARM_WHITE, ], - COLOR_MODE_RGB: [ + ColorMode.RGB: [ LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | LightColorCapability.RGB, ], - COLOR_MODE_RGBW: [ + ColorMode.RGBW: [ LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | LightColorCapability.RGB | LightColorCapability.WHITE, ], - COLOR_MODE_RGBWW: [ + ColorMode.RGBWW: [ LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | LightColorCapability.RGB @@ -93,7 +84,7 @@ _COLOR_MODE_MAPPING = { | LightColorCapability.RGB | LightColorCapability.COLD_WARM_WHITE, ], - COLOR_MODE_WHITE: [ + ColorMode.WHITE: [ LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | LightColorCapability.WHITE @@ -117,7 +108,7 @@ def _color_mode_to_ha(mode: int) -> str: candidates.append((ha_mode, caps)) if not candidates: - return COLOR_MODE_UNKNOWN + return ColorMode.UNKNOWN # choose the color mode with the most bits set candidates.sort(key=lambda key: bin(key[1]).count("1")) @@ -354,26 +345,26 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): @property def supported_features(self) -> int: """Flag supported features.""" - flags = SUPPORT_FLASH + flags: int = LightEntityFeature.FLASH # All color modes except UNKNOWN,ON_OFF support transition modes = self._native_supported_color_modes if any(m not in (0, LightColorCapability.ON_OFF) for m in modes): - flags |= SUPPORT_TRANSITION + flags |= LightEntityFeature.TRANSITION if self._static_info.effects: - flags |= SUPPORT_EFFECT + flags |= LightEntityFeature.EFFECT return flags @property def supported_color_modes(self) -> set[str] | None: """Flag supported color modes.""" supported = set(map(_color_mode_to_ha, self._native_supported_color_modes)) - if COLOR_MODE_ONOFF in supported and len(supported) > 1: - supported.remove(COLOR_MODE_ONOFF) - if COLOR_MODE_BRIGHTNESS in supported and len(supported) > 1: - supported.remove(COLOR_MODE_BRIGHTNESS) - if COLOR_MODE_WHITE in supported and len(supported) == 1: - supported.remove(COLOR_MODE_WHITE) + if ColorMode.ONOFF in supported and len(supported) > 1: + supported.remove(ColorMode.ONOFF) + if ColorMode.BRIGHTNESS in supported and len(supported) > 1: + supported.remove(ColorMode.BRIGHTNESS) + if ColorMode.WHITE in supported and len(supported) == 1: + supported.remove(ColorMode.WHITE) return supported @property diff --git a/homeassistant/components/esphome/lock.py b/homeassistant/components/esphome/lock.py index 84c93d9df13..a85411b5744 100644 --- a/homeassistant/components/esphome/lock.py +++ b/homeassistant/components/esphome/lock.py @@ -5,7 +5,7 @@ from typing import Any from aioesphomeapi import LockCommand, LockEntityState, LockInfo, LockState -from homeassistant.components.lock import SUPPORT_OPEN, LockEntity +from homeassistant.components.lock import LockEntity, LockEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE from homeassistant.core import HomeAssistant @@ -44,7 +44,7 @@ class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity): @property def supported_features(self) -> int: """Flag supported features.""" - return SUPPORT_OPEN if self._static_info.supports_open else 0 + return LockEntityFeature.OPEN if self._static_info.supports_open else 0 @property def code_format(self) -> str | None: diff --git a/homeassistant/components/esphome/translations/cs.json b/homeassistant/components/esphome/translations/cs.json index c6885e06851..cdd86bd2577 100644 --- a/homeassistant/components/esphome/translations/cs.json +++ b/homeassistant/components/esphome/translations/cs.json @@ -11,7 +11,7 @@ "invalid_psk": "Transportn\u00ed \u0161ifrovac\u00ed kl\u00ed\u010d je neplatn\u00fd. Ujist\u011bte se, \u017ee odpov\u00edd\u00e1 tomu, co m\u00e1te ve sv\u00e9 konfiguraci", "resolve_error": "Nelze naj\u00edt IP adresu uzlu ESP. Pokud tato chyba p\u0159etrv\u00e1v\u00e1, nastavte statickou adresu IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, - "flow_title": "ESPHome: {name}", + "flow_title": "{name}", "step": { "authenticate": { "data": { diff --git a/homeassistant/components/esphome/translations/hu.json b/homeassistant/components/esphome/translations/hu.json index 17af0e57d26..e8e6c9b2dc2 100644 --- a/homeassistant/components/esphome/translations/hu.json +++ b/homeassistant/components/esphome/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "error": { diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py index aa37e10cfa3..57d54146137 100644 --- a/homeassistant/components/eufy/light.py +++ b/homeassistant/components/eufy/light.py @@ -7,9 +7,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + ColorMode, LightEntity, ) from homeassistant.core import HomeAssistant @@ -54,11 +52,11 @@ class EufyLight(LightEntity): self._bulb = lakeside.bulb(self._address, self._code, self._type) self._colormode = False if self._type == "T1011": - self._features = SUPPORT_BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} elif self._type == "T1012": - self._features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP - elif self._type == "T1013": - self._features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR + self._attr_supported_color_modes = {ColorMode.COLOR_TEMP} + else: # T1013 + self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} self._bulb.connect() def update(self): @@ -115,14 +113,19 @@ class EufyLight(LightEntity): @property def hs_color(self): """Return the color of this light.""" - if not self._colormode: - return None return self._hs @property - def supported_features(self): - """Flag supported features.""" - return self._features + def color_mode(self) -> str | None: + """Return the color mode of the light.""" + if self._type == "T1011": + return ColorMode.BRIGHTNESS + if self._type == "T1012": + return ColorMode.COLOR_TEMP + # T1013 + if not self._colormode: + return ColorMode.COLOR_TEMP + return ColorMode.HS def turn_on(self, **kwargs): """Turn the specified light on.""" diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py index ad3c5130224..ab195d81530 100644 --- a/homeassistant/components/everlights/light.py +++ b/homeassistant/components/everlights/light.py @@ -12,10 +12,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_HOSTS from homeassistant.core import HomeAssistant @@ -28,8 +27,6 @@ import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -SUPPORT_EVERLIGHTS = SUPPORT_EFFECT | SUPPORT_BRIGHTNESS | SUPPORT_COLOR - SCAN_INTERVAL = timedelta(minutes=1) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -77,6 +74,10 @@ async def async_setup_platform( class EverLightsLight(LightEntity): """Representation of a Flux light.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + _attr_supported_features = LightEntityFeature.EFFECT + def __init__(self, api, channel, status, effects): """Initialize the light.""" self._api = api @@ -125,11 +126,6 @@ class EverLightsLight(LightEntity): """Return the effect property.""" return self._effect - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_EVERLIGHTS - @property def effect_list(self): """Return the list of supported effects.""" diff --git a/homeassistant/components/evil_genius_labs/__init__.py b/homeassistant/components/evil_genius_labs/__init__.py index ff858db566a..da3cbb51717 100644 --- a/homeassistant/components/evil_genius_labs/__init__.py +++ b/homeassistant/components/evil_genius_labs/__init__.py @@ -5,6 +5,7 @@ from datetime import timedelta import logging from typing import cast +from aiohttp import ContentTypeError from async_timeout import timeout import pyevilgenius @@ -54,6 +55,8 @@ class EvilGeniusUpdateCoordinator(DataUpdateCoordinator[dict]): info: dict + product: dict | None + def __init__( self, hass: HomeAssistant, name: str, client: pyevilgenius.EvilGeniusDevice ) -> None: @@ -71,14 +74,30 @@ class EvilGeniusUpdateCoordinator(DataUpdateCoordinator[dict]): """Return the device name.""" return cast(str, self.data["name"]["value"]) + @property + def product_name(self) -> str | None: + """Return the product name.""" + if self.product is None: + return None + + return cast(str, self.product["productName"]) + async def _async_update_data(self) -> dict: """Update Evil Genius data.""" if not hasattr(self, "info"): async with timeout(5): self.info = await self.client.get_info() + if not hasattr(self, "product"): + async with timeout(5): + try: + self.product = await self.client.get_product() + except ContentTypeError: + # Older versions of the API don't support this + self.product = None + async with timeout(5): - return cast(dict, await self.client.get_data()) + return cast(dict, await self.client.get_all()) class EvilGeniusEntity(CoordinatorEntity[EvilGeniusUpdateCoordinator]): @@ -92,6 +111,7 @@ class EvilGeniusEntity(CoordinatorEntity[EvilGeniusUpdateCoordinator]): identifiers={(DOMAIN, info["wiFiChipId"])}, connections={(dr.CONNECTION_NETWORK_MAC, info["macAddress"])}, name=self.coordinator.device_name, + model=self.coordinator.product_name, manufacturer="Evil Genius Labs", sw_version=info["coreVersion"].replace("_", "."), configuration_url=self.coordinator.client.url, diff --git a/homeassistant/components/evil_genius_labs/config_flow.py b/homeassistant/components/evil_genius_labs/config_flow.py index 744e0194ded..53303d738a5 100644 --- a/homeassistant/components/evil_genius_labs/config_flow.py +++ b/homeassistant/components/evil_genius_labs/config_flow.py @@ -32,7 +32,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, try: async with async_timeout.timeout(10): - data = await hub.get_data() + data = await hub.get_all() info = await hub.get_info() except aiohttp.ClientError as err: _LOGGER.debug("Unable to connect: %s", err) diff --git a/homeassistant/components/evil_genius_labs/diagnostics.py b/homeassistant/components/evil_genius_labs/diagnostics.py index c269f699c11..18b8c8ed572 100644 --- a/homeassistant/components/evil_genius_labs/diagnostics.py +++ b/homeassistant/components/evil_genius_labs/diagnostics.py @@ -19,5 +19,5 @@ async def async_get_config_entry_diagnostics( return { "info": async_redact_data(coordinator.info, TO_REDACT), - "data": coordinator.data, + "all": coordinator.data, } diff --git a/homeassistant/components/evil_genius_labs/light.py b/homeassistant/components/evil_genius_labs/light.py index cb837668a4c..41fbcfa9b48 100644 --- a/homeassistant/components/evil_genius_labs/light.py +++ b/homeassistant/components/evil_genius_labs/light.py @@ -6,6 +6,7 @@ from typing import Any, cast from async_timeout import timeout from homeassistant.components import light +from homeassistant.components.light import ColorMode, LightEntity, LightEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -28,14 +29,12 @@ async def async_setup_entry( async_add_entities([EvilGeniusLight(coordinator)]) -class EvilGeniusLight(EvilGeniusEntity, light.LightEntity): +class EvilGeniusLight(EvilGeniusEntity, LightEntity): """Evil Genius Labs light.""" - _attr_supported_features = ( - light.SUPPORT_BRIGHTNESS | light.SUPPORT_EFFECT | light.SUPPORT_COLOR - ) - _attr_supported_color_modes = {light.COLOR_MODE_RGB} - _attr_color_mode = light.COLOR_MODE_RGB + _attr_supported_features = LightEntityFeature.EFFECT + _attr_supported_color_modes = {ColorMode.RGB} + _attr_color_mode = ColorMode.RGB def __init__(self, coordinator: EvilGeniusUpdateCoordinator) -> None: """Initialize the Evil Genius light.""" diff --git a/homeassistant/components/evil_genius_labs/manifest.json b/homeassistant/components/evil_genius_labs/manifest.json index 698c13b43e6..f0c0ba69311 100644 --- a/homeassistant/components/evil_genius_labs/manifest.json +++ b/homeassistant/components/evil_genius_labs/manifest.json @@ -3,7 +3,7 @@ "name": "Evil Genius Labs", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/evil_genius_labs", - "requirements": ["pyevilgenius==1.0.0"], + "requirements": ["pyevilgenius==2.0.0"], "codeowners": ["@balloob"], "iot_class": "local_polling" } diff --git a/homeassistant/components/evil_genius_labs/translations/cs.json b/homeassistant/components/evil_genius_labs/translations/cs.json index e1bf8e7f45f..7a929c1286f 100644 --- a/homeassistant/components/evil_genius_labs/translations/cs.json +++ b/homeassistant/components/evil_genius_labs/translations/cs.json @@ -1,6 +1,7 @@ { "config": { "error": { + "timeout": "Vypr\u0161el \u010dasov\u00fd limit pro nav\u00e1z\u00e1n\u00ed spojen\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" } } diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 98d72f828ae..5f83caa8648 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -524,7 +524,6 @@ class EvoDevice(Entity): self._evo_tcs = evo_broker.tcs self._unique_id = self._name = self._icon = self._precision = None - self._supported_features = None self._device_state_attrs = {} async def async_refresh(self, payload: dict | None = None) -> None: @@ -580,11 +579,6 @@ class EvoDevice(Entity): """Return the icon to use in the frontend UI.""" return self._icon - @property - def supported_features(self) -> int: - """Get the flag of supported features of the device.""" - return self._supported_features - async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" async_dispatcher_connect(self.hass, DOMAIN, self.async_refresh) diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index fa1063f846d..676bc88f470 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -6,15 +6,12 @@ import logging from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO, PRESET_HOME, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import PRECISION_TENTHS from homeassistant.core import HomeAssistant @@ -53,7 +50,7 @@ _LOGGER = logging.getLogger(__name__) PRESET_RESET = "Reset" # reset all child zones to EVO_FOLLOW PRESET_CUSTOM = "Custom" -HA_HVAC_TO_TCS = {HVAC_MODE_OFF: EVO_HEATOFF, HVAC_MODE_HEAT: EVO_AUTO} +HA_HVAC_TO_TCS = {HVACMode.OFF: EVO_HEATOFF, HVACMode.HEAT: EVO_AUTO} TCS_PRESET_TO_HA = { EVO_AWAY: PRESET_AWAY, @@ -167,7 +164,9 @@ class EvoZone(EvoChild, EvoClimateEntity): self._precision = self._evo_device.setpointCapabilities["valueResolution"] self._preset_modes = list(HA_PRESET_TO_EVO) - self._supported_features = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) async def async_zone_svc_request(self, service: dict, data: dict) -> None: """Process a service request (setpoint override) for a zone.""" @@ -199,9 +198,9 @@ class EvoZone(EvoChild, EvoClimateEntity): def hvac_mode(self) -> str: """Return the current operating mode of a Zone.""" if self._evo_tcs.systemModeStatus["mode"] in (EVO_AWAY, EVO_HEATOFF): - return HVAC_MODE_AUTO + return HVACMode.AUTO is_off = self.target_temperature <= self.min_temp - return HVAC_MODE_OFF if is_off else HVAC_MODE_HEAT + return HVACMode.OFF if is_off else HVACMode.HEAT @property def target_temperature(self) -> float: @@ -264,11 +263,11 @@ class EvoZone(EvoChild, EvoClimateEntity): regardless of any override mode, e.g. 'HeatingOff', Zones to (by default) 5C, and 'Away', Zones to (by default) 12C. """ - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._evo_broker.call_client_api( self._evo_device.set_temperature(self.min_temp, until=None) ) - else: # HVAC_MODE_HEAT + else: # HVACMode.HEAT await self._evo_broker.call_client_api( self._evo_device.cancel_temp_override() ) @@ -328,7 +327,9 @@ class EvoController(EvoClimateEntity): self._preset_modes = [ TCS_PRESET_TO_HA[m] for m in modes if m in list(TCS_PRESET_TO_HA) ] - self._supported_features = SUPPORT_PRESET_MODE if self._preset_modes else 0 + self._attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE if self._preset_modes else 0 + ) async def async_tcs_svc_request(self, service: dict, data: dict) -> None: """Process a service request (system mode) for a controller. @@ -363,7 +364,7 @@ class EvoController(EvoClimateEntity): def hvac_mode(self) -> str: """Return the current operating mode of a Controller.""" tcs_mode = self._evo_tcs.systemModeStatus["mode"] - return HVAC_MODE_OFF if tcs_mode == EVO_HEATOFF else HVAC_MODE_HEAT + return HVACMode.OFF if tcs_mode == EVO_HEATOFF else HVACMode.HEAT @property def current_temperature(self) -> float | None: diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 217aa780685..f216862b232 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -4,9 +4,8 @@ from __future__ import annotations import logging from homeassistant.components.water_heater import ( - SUPPORT_AWAY_MODE, - SUPPORT_OPERATION_MODE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant @@ -61,7 +60,9 @@ class EvoDHW(EvoChild, WaterHeaterEntity): self._icon = "mdi:thermometer-lines" self._precision = PRECISION_TENTHS if evo_broker.client_v1 else PRECISION_WHOLE - self._supported_features = SUPPORT_AWAY_MODE | SUPPORT_OPERATION_MODE + self._attr_supported_features = ( + WaterHeaterEntityFeature.AWAY_MODE | WaterHeaterEntityFeature.OPERATION_MODE + ) @property def current_operation(self) -> str: diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index ed48ed4ee03..2fa410e268c 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -7,7 +7,7 @@ from pyezviz.exceptions import HTTPError, InvalidHost, PyEzvizError import voluptuous as vol from homeassistant.components import ffmpeg -from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import get_ffmpeg_manager from homeassistant.config_entries import ( SOURCE_IGNORE, @@ -194,19 +194,14 @@ class EzvizCamera(EzvizEntity, Camera): self._ffmpeg = get_ffmpeg_manager(hass) self._attr_unique_id = serial self._attr_name = self.data["name"] + if camera_password: + self._attr_supported_features = CameraEntityFeature.STREAM @property def available(self) -> bool: """Return True if entity is available.""" return self.data["status"] != 2 - @property - def supported_features(self) -> int: - """Return supported features.""" - if self._password: - return SUPPORT_STREAM - return 0 - @property def is_on(self) -> bool: """Return true if on.""" diff --git a/homeassistant/components/ezviz/switch.py b/homeassistant/components/ezviz/switch.py index c2e562f62da..55a946f858a 100644 --- a/homeassistant/components/ezviz/switch.py +++ b/homeassistant/components/ezviz/switch.py @@ -65,7 +65,7 @@ class EzvizSwitch(EzvizEntity, SwitchEntity): ) except (HTTPError, PyEzvizError) as err: - raise PyEzvizError("Failed to turn on switch {self._name}") from err + raise PyEzvizError(f"Failed to turn on switch {self._name}") from err if update_ok: await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/ezviz/translations/it.json b/homeassistant/components/ezviz/translations/it.json index 0c6ce669ba3..febba0cad51 100644 --- a/homeassistant/components/ezviz/translations/it.json +++ b/homeassistant/components/ezviz/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured_account": "L'account \u00e8 gi\u00e0 configurato", - "ezviz_cloud_account_missing": "Ezviz cloud account mancante. Si prega di riconfigurare l'account Ezviz cloud", + "ezviz_cloud_account_missing": "Ezviz cloud account mancante. Riconfigura l'account Ezviz cloud", "unknown": "Errore imprevisto" }, "error": { diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 89de0cabb10..5eb3bedabd5 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta +from enum import IntEnum import functools as ft import logging import math @@ -39,7 +40,18 @@ SCAN_INTERVAL = timedelta(seconds=30) ENTITY_ID_FORMAT = DOMAIN + ".{}" -# Bitfield of features supported by the fan entity + +class FanEntityFeature(IntEnum): + """Supported features of the fan entity.""" + + SET_SPEED = 1 + OSCILLATE = 2 + DIRECTION = 4 + PRESET_MODE = 8 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the FanEntityFeature enum instead. SUPPORT_SET_SPEED = 1 SUPPORT_OSCILLATE = 2 SUPPORT_DIRECTION = 4 @@ -103,7 +115,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) }, "async_increase_speed", - [SUPPORT_SET_SPEED], + [FanEntityFeature.SET_SPEED], ) component.async_register_entity_service( SERVICE_DECREASE_SPEED, @@ -113,19 +125,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) }, "async_decrease_speed", - [SUPPORT_SET_SPEED], + [FanEntityFeature.SET_SPEED], ) component.async_register_entity_service( SERVICE_OSCILLATE, {vol.Required(ATTR_OSCILLATING): cv.boolean}, "async_oscillate", - [SUPPORT_OSCILLATE], + [FanEntityFeature.OSCILLATE], ) component.async_register_entity_service( SERVICE_SET_DIRECTION, {vol.Optional(ATTR_DIRECTION): cv.string}, "async_set_direction", - [SUPPORT_DIRECTION], + [FanEntityFeature.DIRECTION], ) component.async_register_entity_service( SERVICE_SET_PERCENTAGE, @@ -135,13 +147,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) }, "async_set_percentage", - [SUPPORT_SET_SPEED], + [FanEntityFeature.SET_SPEED], ) component.async_register_entity_service( SERVICE_SET_PRESET_MODE, {vol.Required(ATTR_PRESET_MODE): cv.string}, "async_set_preset_mode", - [SUPPORT_SET_SPEED, SUPPORT_PRESET_MODE], + [FanEntityFeature.SET_SPEED, FanEntityFeature.PRESET_MODE], ) return True @@ -314,8 +326,8 @@ class FanEntity(ToggleEntity): attrs = {} if ( - self.supported_features & SUPPORT_SET_SPEED - or self.supported_features & SUPPORT_PRESET_MODE + self.supported_features & FanEntityFeature.SET_SPEED + or self.supported_features & FanEntityFeature.PRESET_MODE ): attrs[ATTR_PRESET_MODES] = self.preset_modes @@ -328,19 +340,19 @@ class FanEntity(ToggleEntity): data: dict[str, float | str | None] = {} supported_features = self.supported_features - if supported_features & SUPPORT_DIRECTION: + if supported_features & FanEntityFeature.DIRECTION: data[ATTR_DIRECTION] = self.current_direction - if supported_features & SUPPORT_OSCILLATE: + if supported_features & FanEntityFeature.OSCILLATE: data[ATTR_OSCILLATING] = self.oscillating - if supported_features & SUPPORT_SET_SPEED: + if supported_features & FanEntityFeature.SET_SPEED: data[ATTR_PERCENTAGE] = self.percentage data[ATTR_PERCENTAGE_STEP] = self.percentage_step if ( - supported_features & SUPPORT_PRESET_MODE - or supported_features & SUPPORT_SET_SPEED + supported_features & FanEntityFeature.PRESET_MODE + or supported_features & FanEntityFeature.SET_SPEED ): data[ATTR_PRESET_MODE] = self.preset_mode @@ -355,7 +367,7 @@ class FanEntity(ToggleEntity): def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite. - Requires SUPPORT_SET_SPEED. + Requires FanEntityFeature.SET_SPEED. """ if hasattr(self, "_attr_preset_mode"): return self._attr_preset_mode @@ -365,7 +377,7 @@ class FanEntity(ToggleEntity): def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. - Requires SUPPORT_SET_SPEED. + Requires FanEntityFeature.SET_SPEED. """ if hasattr(self, "_attr_preset_modes"): return self._attr_preset_modes diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index be018aa4b54..a12b23cb16d 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from collections.abc import Iterable, Mapping +from collections.abc import Iterable import logging from typing import Any @@ -30,13 +30,18 @@ from . import ( _LOGGER = logging.getLogger(__name__) VALID_STATES = {STATE_ON, STATE_OFF} -ATTRIBUTES = { # attribute: service - ATTR_DIRECTION: SERVICE_SET_DIRECTION, - ATTR_OSCILLATING: SERVICE_OSCILLATE, + +# These are used as parameters to fan.turn_on service. +SPEED_AND_MODE_ATTRIBUTES = { ATTR_PERCENTAGE: SERVICE_SET_PERCENTAGE, ATTR_PRESET_MODE: SERVICE_SET_PRESET_MODE, } +SIMPLE_ATTRIBUTES = { # attribute: service + ATTR_DIRECTION: SERVICE_SET_DIRECTION, + ATTR_OSCILLATING: SERVICE_OSCILLATE, +} + async def _async_reproduce_state( hass: HomeAssistant, @@ -56,37 +61,49 @@ async def _async_reproduce_state( ) return - # Return if we are already at the right state. - if cur_state.state == state.state and all( - check_attr_equal(cur_state.attributes, state.attributes, attr) - for attr in ATTRIBUTES - ): - return - - service_data = {ATTR_ENTITY_ID: state.entity_id} - service_calls = {} # service: service_data + service_calls: dict[str, dict[str, Any]] = {} if state.state == STATE_ON: # The fan should be on if cur_state.state != STATE_ON: - # Turn on the fan at first - service_calls[SERVICE_TURN_ON] = service_data + # Turn on the fan with all the speed and modes attributes. + # The `turn_on` method will figure out in which mode to + # turn the fan on. + service_calls[SERVICE_TURN_ON] = { + attr: state.attributes.get(attr) + for attr in SPEED_AND_MODE_ATTRIBUTES + if state.attributes.get(attr) is not None + } + else: + # If the fan is already on, we need to set speed or mode + # based on the state. + # + # Speed and preset mode are mutually exclusive, so one of + # them is always going to be stored as None. If we were to + # try to set it, it will raise an error. So instead we + # only update the one that is non-None. + for attr, service in SPEED_AND_MODE_ATTRIBUTES.items(): + value = state.attributes.get(attr) + if value is not None and value != cur_state.attributes.get(attr): + service_calls[service] = {attr: value} - for attr, service in ATTRIBUTES.items(): - # Call services to adjust the attributes - if attr in state.attributes and not check_attr_equal( - state.attributes, cur_state.attributes, attr - ): - data = service_data.copy() - data[attr] = state.attributes[attr] - service_calls[service] = data - - elif state.state == STATE_OFF: - service_calls[SERVICE_TURN_OFF] = service_data + # The simple attributes are copied directly. They can only be + # None if the fan does not support the feature in the first + # place, so the equality check ensures we don't call the + # services with invalid parameters. + for attr, service in SIMPLE_ATTRIBUTES.items(): + if (value := state.attributes.get(attr)) != cur_state.attributes.get(attr): + service_calls[service] = {attr: value} + elif state.state == STATE_OFF and cur_state.state != state.state: + service_calls[SERVICE_TURN_OFF] = {} for service, data in service_calls.items(): await hass.services.async_call( - DOMAIN, service, data, context=context, blocking=True + DOMAIN, + service, + {ATTR_ENTITY_ID: state.entity_id, **data}, + context=context, + blocking=True, ) @@ -106,8 +123,3 @@ async def async_reproduce_states( for state in states ) ) - - -def check_attr_equal(attr1: Mapping, attr2: Mapping, attr_str: str) -> bool: - """Return true if the given attributes are equal.""" - return attr1.get(attr_str) == attr2.get(attr_str) diff --git a/homeassistant/components/fan/translations/hu.json b/homeassistant/components/fan/translations/hu.json index ec3175b8290..50e659b001a 100644 --- a/homeassistant/components/fan/translations/hu.json +++ b/homeassistant/components/fan/translations/hu.json @@ -9,6 +9,7 @@ "is_on": "{entity_name} be van kapcsolva" }, "trigger_type": { + "changed_states": "{entity_name} be- vagy kikapcsolt", "toggled": "{entity_name} \u00e1tkapcsolt", "turned_off": "{entity_name} ki lett kapcsolva", "turned_on": "{entity_name} be lett kapcsolva" diff --git a/homeassistant/components/ffmpeg/camera.py b/homeassistant/components/ffmpeg/camera.py index 6e41d88fc85..a15dbe654b0 100644 --- a/homeassistant/components/ffmpeg/camera.py +++ b/homeassistant/components/ffmpeg/camera.py @@ -5,7 +5,7 @@ from haffmpeg.camera import CameraMjpeg from haffmpeg.tools import IMAGE_JPEG import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, CameraEntityFeature from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream @@ -40,6 +40,8 @@ async def async_setup_platform( class FFmpegCamera(Camera): """An implementation of an FFmpeg camera.""" + _attr_supported_features = CameraEntityFeature.STREAM + def __init__(self, hass, config): """Initialize a FFmpeg camera.""" super().__init__() @@ -49,11 +51,6 @@ class FFmpegCamera(Camera): self._input = config.get(CONF_INPUT) self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS) - @property - def supported_features(self): - """Return supported features.""" - return SUPPORT_STREAM - async def stream_source(self): """Return the stream source.""" return self._input.split(" ")[-1] diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 1db73538fa0..d5a8f94970d 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -2,10 +2,18 @@ from __future__ import annotations from collections import defaultdict +from collections.abc import Mapping import logging from typing import Any -from fiblary3.client.v4.client import Client as FibaroClient, StateHandler +from fiblary3.client.v4.client import ( + Client as FibaroClientV4, + StateHandler as StateHandlerV4, +) +from fiblary3.client.v5.client import ( + Client as FibaroClientV5, + StateHandler as StateHandlerV5, +) from fiblary3.common.exceptions import HTTPException import voluptuous as vol @@ -53,26 +61,26 @@ PLATFORMS = [ ] FIBARO_TYPEMAP = { - "com.fibaro.multilevelSensor": "sensor", - "com.fibaro.binarySwitch": "switch", - "com.fibaro.multilevelSwitch": "switch", - "com.fibaro.FGD212": "light", - "com.fibaro.FGR": "cover", - "com.fibaro.doorSensor": "binary_sensor", - "com.fibaro.doorWindowSensor": "binary_sensor", - "com.fibaro.FGMS001": "binary_sensor", - "com.fibaro.heatDetector": "binary_sensor", - "com.fibaro.lifeDangerSensor": "binary_sensor", - "com.fibaro.smokeSensor": "binary_sensor", - "com.fibaro.remoteSwitch": "switch", - "com.fibaro.sensor": "sensor", - "com.fibaro.colorController": "light", - "com.fibaro.securitySensor": "binary_sensor", - "com.fibaro.hvac": "climate", - "com.fibaro.setpoint": "climate", - "com.fibaro.FGT001": "climate", - "com.fibaro.thermostatDanfoss": "climate", - "com.fibaro.doorLock": "lock", + "com.fibaro.multilevelSensor": Platform.SENSOR, + "com.fibaro.binarySwitch": Platform.SWITCH, + "com.fibaro.multilevelSwitch": Platform.SWITCH, + "com.fibaro.FGD212": Platform.LIGHT, + "com.fibaro.FGR": Platform.COVER, + "com.fibaro.doorSensor": Platform.BINARY_SENSOR, + "com.fibaro.doorWindowSensor": Platform.BINARY_SENSOR, + "com.fibaro.FGMS001": Platform.BINARY_SENSOR, + "com.fibaro.heatDetector": Platform.BINARY_SENSOR, + "com.fibaro.lifeDangerSensor": Platform.BINARY_SENSOR, + "com.fibaro.smokeSensor": Platform.BINARY_SENSOR, + "com.fibaro.remoteSwitch": Platform.SWITCH, + "com.fibaro.sensor": Platform.SENSOR, + "com.fibaro.colorController": Platform.LIGHT, + "com.fibaro.securitySensor": Platform.BINARY_SENSOR, + "com.fibaro.hvac": Platform.CLIMATE, + "com.fibaro.setpoint": Platform.CLIMATE, + "com.fibaro.FGT001": Platform.CLIMATE, + "com.fibaro.thermostatDanfoss": Platform.CLIMATE, + "com.fibaro.doorLock": Platform.LOCK, } DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema( @@ -118,18 +126,43 @@ CONFIG_SCHEMA = vol.Schema( class FibaroController: """Initiate Fibaro Controller Class.""" - def __init__(self, config): - """Initialize the Fibaro controller.""" - self._client = FibaroClient( - config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD] - ) + def __init__( + self, config: Mapping[str, Any], serial_number: str | None = None + ) -> None: + """Initialize the Fibaro controller. + + Version 4 is used for home center 2 (SN starts with HC2) and + home center lite (SN starts with HCL). + + Version 5 is used for home center 3 (SN starts with HC3), + home center 3 lite (SN starts with HC3L) and yubii home (SN starts with YH). + + Here the serial number is optional and we choose then the V4 client. You + should do that only when you use the FibaroController for login test as only + the login and info API's are equal throughout the different versions. + """ + if ( + serial_number is None + or serial_number.upper().startswith("HC2") + or serial_number.upper().startswith("HCL") + ): + self._client = FibaroClientV4( + config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD] + ) + else: + self._client = FibaroClientV5( + config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD] + ) + self._scene_map = None # Whether to import devices from plugins self._import_plugins = config[CONF_IMPORT_PLUGINS] self._room_map = None # Mapping roomId to room object self._device_map = None # Mapping deviceId to device object - self.fibaro_devices = None # List of devices by type - self._callbacks = {} # Update value callbacks by deviceId + self.fibaro_devices: dict[Platform, list] = defaultdict( + list + ) # List of devices by entity platform + self._callbacks: dict[Any, Any] = {} # Update value callbacks by deviceId self._state_handler = None # Fiblary's StateHandler object self.hub_serial = None # Unique serial number of the hub self.name = None # The friendly name of the hub @@ -174,7 +207,10 @@ class FibaroController: def enable_state_handler(self): """Start StateHandler thread for monitoring updates.""" - self._state_handler = StateHandler(self._client, self._on_state_change) + if isinstance(self._client, FibaroClientV4): + self._state_handler = StateHandlerV4(self._client, self._on_state_change) + else: + self._state_handler = StateHandlerV5(self._client, self._on_state_change) def disable_state_handler(self): """Stop StateHandler thread used for monitoring updates.""" @@ -247,35 +283,35 @@ class FibaroController: return self.get_children(self._device_map[device.id].parentId) @staticmethod - def _map_device_to_type(device): + def _map_device_to_platform(device: Any) -> Platform | None: """Map device to HA device type.""" # Use our lookup table to identify device type - device_type = None + platform: Platform | None = None if "type" in device: - device_type = FIBARO_TYPEMAP.get(device.type) - if device_type is None and "baseType" in device: - device_type = FIBARO_TYPEMAP.get(device.baseType) + platform = FIBARO_TYPEMAP.get(device.type) + if platform is None and "baseType" in device: + platform = FIBARO_TYPEMAP.get(device.baseType) # We can also identify device type by its capabilities - if device_type is None: + if platform is None: if "setBrightness" in device.actions: - device_type = "light" + platform = Platform.LIGHT elif "turnOn" in device.actions: - device_type = "switch" + platform = Platform.SWITCH elif "open" in device.actions: - device_type = "cover" + platform = Platform.COVER elif "secure" in device.actions: - device_type = "lock" + platform = Platform.LOCK elif "value" in device.properties: if device.properties.value in ("true", "false"): - device_type = "binary_sensor" + platform = Platform.BINARY_SENSOR else: - device_type = "sensor" + platform = Platform.SENSOR # Switches that control lights should show up as lights - if device_type == "switch" and device.properties.get("isLight", False): - device_type = "light" - return device_type + if platform == Platform.SWITCH and device.properties.get("isLight", False): + platform = Platform.LIGHT + return platform def _read_scenes(self): scenes = self._client.scenes.list() @@ -295,14 +331,13 @@ class FibaroController: ) device.unique_id_str = f"{self.hub_serial}.scene.{device.id}" self._scene_map[device.id] = device - self.fibaro_devices["scene"].append(device) + self.fibaro_devices[Platform.SCENE].append(device) _LOGGER.debug("%s scene -> %s", device.ha_id, device) def _read_devices(self): """Read and process the device list.""" devices = self._client.devices.list() self._device_map = {} - self.fibaro_devices = defaultdict(list) last_climate_parent = None last_endpoint = None for device in devices: @@ -323,10 +358,10 @@ class FibaroController: "isPlugin" not in device or (not device.isPlugin or self._import_plugins) ): - device.mapped_type = self._map_device_to_type(device) + device.mapped_platform = self._map_device_to_platform(device) else: - device.mapped_type = None - if (dtype := device.mapped_type) is None: + device.mapped_platform = None + if (platform := device.mapped_platform) is None: continue device.unique_id_str = f"{self.hub_serial}.{device.id}" self._device_map[device.id] = device @@ -335,11 +370,11 @@ class FibaroController: device.ha_id, device.type, device.baseType, - dtype, + platform, str(device), ) - if dtype != "climate": - self.fibaro_devices[dtype].append(device) + if platform != Platform.CLIMATE: + self.fibaro_devices[platform].append(device) continue # We group climate devices into groups with the same # endPointID belonging to the same parent device. @@ -360,7 +395,7 @@ class FibaroController: and last_endpoint != device.properties.endPointId ): _LOGGER.debug("Handle separately") - self.fibaro_devices[dtype].append(device) + self.fibaro_devices[platform].append(device) last_climate_parent = device.parentId if "endPointId" in device.properties: last_endpoint = device.properties.endPointId @@ -403,17 +438,24 @@ async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool: return True -def _init_controller(data: dict[str, Any]) -> FibaroController: +def _init_controller( + data: Mapping[str, Any], serial_number: str | None +) -> FibaroController: """Validate the user input allows us to connect to fibaro.""" - controller = FibaroController(data) + controller = FibaroController(data, serial_number) controller.connect_with_error_handling() return controller async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up the Fibaro Component.""" + """Set up the Fibaro Component. + + The unique id of the config entry is the serial number of the home center. + """ try: - controller = await hass.async_add_executor_job(_init_controller, entry.data) + controller = await hass.async_add_executor_job( + _init_controller, entry.data, entry.unique_id + ) except FibaroConnectFailed as connect_ex: raise ConfigEntryNotReady( f"Could not connect to controller at {entry.data[CONF_URL]}" diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index 1b07d3671ae..3c423dc0ce8 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -14,7 +15,7 @@ from . import FIBARO_DEVICES, FibaroDevice from .const import DOMAIN SENSOR_TYPES = { - "com.fibaro.floodSensor": ["Flood", "mdi:water", "flood"], + "com.fibaro.floodSensor": ["Flood", "mdi:water", BinarySensorDeviceClass.MOISTURE], "com.fibaro.motionSensor": ["Motion", "mdi:run", BinarySensorDeviceClass.MOTION], "com.fibaro.doorSensor": ["Door", "mdi:window-open", BinarySensorDeviceClass.DOOR], "com.fibaro.windowSensor": [ @@ -24,7 +25,7 @@ SENSOR_TYPES = { ], "com.fibaro.smokeSensor": ["Smoke", "mdi:smoking", BinarySensorDeviceClass.SMOKE], "com.fibaro.FGMS001": ["Motion", "mdi:run", BinarySensorDeviceClass.MOTION], - "com.fibaro.heatDetector": ["Heat", "mdi:fire", "heat"], + "com.fibaro.heatDetector": ["Heat", "mdi:fire", BinarySensorDeviceClass.HEAT], } @@ -38,7 +39,7 @@ async def async_setup_entry( [ FibaroBinarySensor(device) for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ - "binary_sensor" + Platform.BINARY_SENSOR ] ], True, @@ -50,7 +51,6 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorEntity): def __init__(self, fibaro_device): """Initialize the binary_sensor.""" - self._state = None super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) stype = None @@ -59,27 +59,9 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorEntity): elif fibaro_device.baseType in SENSOR_TYPES: stype = fibaro_device.baseType if stype: - self._device_class = SENSOR_TYPES[stype][2] - self._icon = SENSOR_TYPES[stype][1] - else: - self._device_class = None - self._icon = None - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return self._icon - - @property - def device_class(self): - """Return the device class of the sensor.""" - return self._device_class - - @property - def is_on(self): - """Return true if sensor is on.""" - return self._state + self._attr_device_class = SENSOR_TYPES[stype][2] + self._attr_icon = SENSOR_TYPES[stype][1] def update(self): """Get the latest data and update the state.""" - self._state = self.current_binary_state + self._attr_is_on = self.current_binary_state diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index b639324acd0..c685945fa45 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -5,20 +5,18 @@ import logging from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + ATTR_TEMPERATURE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + Platform, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -72,30 +70,30 @@ OPMODES_PRESET = { HA_OPMODES_PRESET = {v: k for k, v in OPMODES_PRESET.items()} OPMODES_HVAC = { - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, - 3: HVAC_MODE_AUTO, - 4: HVAC_MODE_HEAT, - 5: HVAC_MODE_AUTO, - 6: HVAC_MODE_FAN_ONLY, - 7: HVAC_MODE_HEAT, - 8: HVAC_MODE_DRY, - 9: HVAC_MODE_DRY, - 10: HVAC_MODE_AUTO, - 11: HVAC_MODE_HEAT, - 12: HVAC_MODE_COOL, - 13: HVAC_MODE_AUTO, - 15: HVAC_MODE_AUTO, - 31: HVAC_MODE_HEAT, + 0: HVACMode.OFF, + 1: HVACMode.HEAT, + 2: HVACMode.COOL, + 3: HVACMode.AUTO, + 4: HVACMode.HEAT, + 5: HVACMode.AUTO, + 6: HVACMode.FAN_ONLY, + 7: HVACMode.HEAT, + 8: HVACMode.DRY, + 9: HVACMode.DRY, + 10: HVACMode.AUTO, + 11: HVACMode.HEAT, + 12: HVACMode.COOL, + 13: HVACMode.AUTO, + 15: HVACMode.AUTO, + 31: HVACMode.HEAT, } HA_OPMODES_HVAC = { - HVAC_MODE_OFF: 0, - HVAC_MODE_HEAT: 1, - HVAC_MODE_COOL: 2, - HVAC_MODE_AUTO: 3, - HVAC_MODE_FAN_ONLY: 6, + HVACMode.OFF: 0, + HVACMode.HEAT: 1, + HVACMode.COOL: 2, + HVACMode.AUTO: 3, + HVACMode.FAN_ONLY: 6, } @@ -108,7 +106,9 @@ async def async_setup_entry( async_add_entities( [ FibaroThermostat(device) - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["climate"] + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ + Platform.CLIMATE + ] ], True, ) @@ -157,16 +157,16 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): or "setHeatingThermostatSetpoint" in device.actions ): self._target_temp_device = FibaroDevice(device) - self._support_flags |= SUPPORT_TARGET_TEMPERATURE + self._support_flags |= ClimateEntityFeature.TARGET_TEMPERATURE tempunit = device.properties.unit if "setMode" in device.actions or "setOperatingMode" in device.actions: self._op_mode_device = FibaroDevice(device) - self._support_flags |= SUPPORT_PRESET_MODE + self._support_flags |= ClimateEntityFeature.PRESET_MODE if "setFanMode" in device.actions: self._fan_mode_device = FibaroDevice(device) - self._support_flags |= SUPPORT_FAN_MODE + self._support_flags |= ClimateEntityFeature.FAN_MODE if tempunit == "F": self._unit_of_temp = TEMP_FAHRENHEIT @@ -267,7 +267,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): def hvac_modes(self): """Return the list of available operation modes.""" if not self._op_mode_device: - return [HVAC_MODE_AUTO] # Default to this + return [HVACMode.AUTO] # Default to this return self._hvac_support def set_hvac_mode(self, hvac_mode): @@ -286,7 +286,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): def preset_mode(self): """Return the current preset mode, e.g., home, away, temp. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ if not self._op_mode_device: return None @@ -304,7 +304,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): def preset_modes(self): """Return a list of available preset modes. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ if not self._op_mode_device: return None diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index b87ae1cbf47..e347d0368d2 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -8,6 +8,7 @@ from homeassistant.components.cover import ( CoverEntity, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -24,7 +25,9 @@ async def async_setup_entry( async_add_entities( [ FibaroCover(device) - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["cover"] + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ + Platform.COVER + ] ], True, ) diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index e117eac8a90..ad41b2aaed6 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -7,38 +7,42 @@ from functools import partial from homeassistant.components.light import ( ATTR_BRIGHTNESS, - ATTR_HS_COLOR, - ATTR_WHITE_VALUE, + ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, ENTITY_ID_FORMAT, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_WHITE_VALUE, + ColorMode, LightEntity, + brightness_supported, + color_supported, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -import homeassistant.util.color as color_util from . import FIBARO_DEVICES, FibaroDevice from .const import DOMAIN -def scaleto255(value): +def scaleto255(value: int | None) -> int: """Scale the input value from 0-100 to 0-255.""" + if value is None: + return 0 # Fibaro has a funny way of storing brightness either 0-100 or 0-99 # depending on device type (e.g. dimmer vs led) if value > 98: value = 100 - return max(0, min(255, ((value * 255.0) / 100.0))) + return round(value * 2.55) -def scaleto100(value): - """Scale the input value from 0-255 to 0-100.""" +def scaleto99(value: int | None) -> int: + """Scale the input value from 0-255 to 0-99.""" + if value is None: + return 0 # Make sure a low but non-zero value is not rounded down to zero if 0 < value < 3: return 1 - return max(0, min(100, ((value * 100.0) / 255.0))) + return min(round(value / 2.55), 99) async def async_setup_entry( @@ -50,7 +54,9 @@ async def async_setup_entry( async_add_entities( [ FibaroLight(device) - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["light"] + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ + Platform.LIGHT + ] ], True, ) @@ -61,14 +67,8 @@ class FibaroLight(FibaroDevice, LightEntity): def __init__(self, fibaro_device): """Initialize the light.""" - self._brightness = None - self._color = (0, 0) - self._last_brightness = 0 - self._supported_flags = 0 self._update_lock = asyncio.Lock() - self._white = 0 - self._reset_color = False supports_color = ( "color" in fibaro_device.properties or "colorComponents" in fibaro_device.properties @@ -84,43 +84,24 @@ class FibaroLight(FibaroDevice, LightEntity): or "RGBW" in fibaro_device.type or "rgbw" in fibaro_device.type ) - supports_dimming = ( - "levelChange" in fibaro_device.interfaces - or supports_color - or supports_white_v - ) + supports_dimming = "levelChange" in fibaro_device.interfaces - # Configuration can override default capability detection - if supports_dimming: - self._supported_flags |= SUPPORT_BRIGHTNESS - if supports_color: - self._supported_flags |= SUPPORT_COLOR - if supports_white_v: - self._supported_flags |= SUPPORT_WHITE_VALUE + if supports_color and supports_white_v: + self._attr_supported_color_modes = {ColorMode.RGBW} + self._attr_color_mode = ColorMode.RGBW + elif supports_color: + self._attr_supported_color_modes = {ColorMode.RGB} + self._attr_color_mode = ColorMode.RGB + elif supports_dimming: + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS + else: + self._attr_supported_color_modes = {ColorMode.ONOFF} + self._attr_color_mode = ColorMode.ONOFF super().__init__(fibaro_device) self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id) - @property - def brightness(self): - """Return the brightness of the light.""" - return scaleto255(self._brightness) - - @property - def hs_color(self): - """Return the color of the light.""" - return self._color - - @property - def white_value(self): - """Return the white value of this light between 0..255.""" - return self._white - - @property - def supported_features(self): - """Flag supported features.""" - return self._supported_flags - async def async_turn_on(self, **kwargs): """Turn the light on.""" async with self._update_lock: @@ -128,49 +109,20 @@ class FibaroLight(FibaroDevice, LightEntity): def _turn_on(self, **kwargs): """Really turn the light on.""" - if self._supported_flags & SUPPORT_BRIGHTNESS: - target_brightness = kwargs.get(ATTR_BRIGHTNESS) - - # No brightness specified, so we either restore it to - # last brightness or switch it on at maximum level - if target_brightness is None: - if self._brightness == 0: - if self._last_brightness: - self._brightness = self._last_brightness - else: - self._brightness = 100 - else: - # We set it to the target brightness and turn it on - self._brightness = scaleto100(target_brightness) - - if self._supported_flags & SUPPORT_COLOR and ( - kwargs.get(ATTR_WHITE_VALUE) is not None - or kwargs.get(ATTR_HS_COLOR) is not None - ): - if self._reset_color: - self._color = (100, 0) + if ATTR_BRIGHTNESS in kwargs: + self._attr_brightness = kwargs[ATTR_BRIGHTNESS] + self.set_level(scaleto99(self._attr_brightness)) + if ATTR_RGB_COLOR in kwargs: # Update based on parameters - self._white = kwargs.get(ATTR_WHITE_VALUE, self._white) - self._color = kwargs.get(ATTR_HS_COLOR, self._color) - rgb = color_util.color_hs_to_RGB(*self._color) - self.call_set_color( - round(rgb[0]), - round(rgb[1]), - round(rgb[2]), - round(self._white), - ) - - if self.state == "off": - self.set_level(min(int(self._brightness), 99)) + self._attr_rgb_color = kwargs[ATTR_RGB_COLOR] + self.call_set_color(*self._attr_rgb_color, 0) return - if self._reset_color: - bri255 = scaleto255(self._brightness) - self.call_set_color(bri255, bri255, bri255, bri255) - - if self._supported_flags & SUPPORT_BRIGHTNESS: - self.set_level(min(int(self._brightness), 99)) + if ATTR_RGBW_COLOR in kwargs: + # Update based on parameters + self._attr_rgbw_color = kwargs[ATTR_RGBW_COLOR] + self.call_set_color(*self._attr_rgbw_color) return # The simplest case is left for last. No dimming, just switch on @@ -183,14 +135,6 @@ class FibaroLight(FibaroDevice, LightEntity): def _turn_off(self, **kwargs): """Really turn the light off.""" - # Let's save the last brightness level before we switch it off - if ( - (self._supported_flags & SUPPORT_BRIGHTNESS) - and self._brightness - and self._brightness > 0 - ): - self._last_brightness = self._brightness - self._brightness = 0 self.call_turn_off() @property @@ -225,15 +169,12 @@ class FibaroLight(FibaroDevice, LightEntity): def _update(self): """Really update the state.""" # Brightness handling - if self._supported_flags & SUPPORT_BRIGHTNESS: - self._brightness = float(self.fibaro_device.properties.value) - # Fibaro might report 0-99 or 0-100 for brightness, - # based on device type, so we round up here - if self._brightness > 99: - self._brightness = 100 + if brightness_supported(self.supported_color_modes): + self._attr_brightness = scaleto255(int(self.fibaro_device.properties.value)) + # Color handling if ( - self._supported_flags & SUPPORT_COLOR + color_supported(self.supported_color_modes) and "color" in self.fibaro_device.properties and "," in self.fibaro_device.properties.color ): @@ -242,7 +183,8 @@ class FibaroLight(FibaroDevice, LightEntity): if rgbw_s == "0,0,0,0" and "lastColorSet" in self.fibaro_device.properties: rgbw_s = self.fibaro_device.properties.lastColorSet rgbw_list = [int(i) for i in rgbw_s.split(",")][:4] - if rgbw_list[0] or rgbw_list[1] or rgbw_list[2]: - self._color = color_util.color_RGB_to_hs(*rgbw_list[:3]) - if (self._supported_flags & SUPPORT_WHITE_VALUE) and self.brightness != 0: - self._white = min(255, max(0, rgbw_list[3])) + + if self._attr_color_mode == ColorMode.RGB: + self._attr_rgb_color = tuple(*rgbw_list[:3]) + else: + self._attr_rgbw_color = tuple(rgbw_list) diff --git a/homeassistant/components/fibaro/lock.py b/homeassistant/components/fibaro/lock.py index 5b86a99cbc9..aed7017ba61 100644 --- a/homeassistant/components/fibaro/lock.py +++ b/homeassistant/components/fibaro/lock.py @@ -3,6 +3,7 @@ from __future__ import annotations from homeassistant.components.lock import ENTITY_ID_FORMAT, LockEntity from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -19,7 +20,9 @@ async def async_setup_entry( async_add_entities( [ FibaroLock(device) - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["lock"] + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ + Platform.LOCK + ] ], True, ) diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py index cc476a29cbb..e4e8b19d308 100644 --- a/homeassistant/components/fibaro/scene.py +++ b/homeassistant/components/fibaro/scene.py @@ -5,6 +5,7 @@ from typing import Any from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -21,7 +22,9 @@ async def async_setup_entry( async_add_entities( [ FibaroScene(scene) - for scene in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["scene"] + for scene in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ + Platform.SCENE + ] ], True, ) diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index 45d33728ab9..bd80a9d0181 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -18,6 +18,7 @@ from homeassistant.const import ( POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT, + Platform, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -63,10 +64,10 @@ async def async_setup_entry( ) -> None: """Set up the Fibaro controller devices.""" entities: list[SensorEntity] = [] - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["sensor"]: + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][Platform.SENSOR]: entities.append(FibaroSensor(device)) - for device_type in ("cover", "light", "switch"): - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][device_type]: + for platform in (Platform.COVER, Platform.LIGHT, Platform.SWITCH): + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][platform]: if "energy" in device.interfaces: entities.append(FibaroEnergySensor(device)) if "power" in device.interfaces: diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py index 73056c095c6..fe2b35866b0 100644 --- a/homeassistant/components/fibaro/switch.py +++ b/homeassistant/components/fibaro/switch.py @@ -3,6 +3,7 @@ from __future__ import annotations from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -19,7 +20,9 @@ async def async_setup_entry( async_add_entities( [ FibaroSwitch(device) - for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES]["switch"] + for device in hass.data[DOMAIN][entry.entry_id][FIBARO_DEVICES][ + Platform.SWITCH + ] ], True, ) diff --git a/homeassistant/components/fibaro/translations/bg.json b/homeassistant/components/fibaro/translations/bg.json new file mode 100644 index 00000000000..9f39b8c9185 --- /dev/null +++ b/homeassistant/components/fibaro/translations/bg.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "url": "URL \u0432\u044a\u0432 \u0444\u043e\u0440\u043c\u0430\u0442 URL", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/ca.json b/homeassistant/components/fibaro/translations/ca.json new file mode 100644 index 00000000000..8e04b3567f8 --- /dev/null +++ b/homeassistant/components/fibaro/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "import_plugins": "Vols importar les entitats dels complements Fibaro?", + "password": "Contrasenya", + "url": "URL en el format http://HOST/api/", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/de.json b/homeassistant/components/fibaro/translations/de.json new file mode 100644 index 00000000000..831abc85929 --- /dev/null +++ b/homeassistant/components/fibaro/translations/de.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "import_plugins": "Entit\u00e4ten aus Fibaro-Plugins importieren?", + "password": "Passwort", + "url": "URL im Format http://HOST/api/", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/el.json b/homeassistant/components/fibaro/translations/el.json new file mode 100644 index 00000000000..d2a2659646f --- /dev/null +++ b/homeassistant/components/fibaro/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "import_plugins": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03b1\u03c0\u03cc \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b1 fibaro;", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03bc\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae http://HOST/api/", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/en.json b/homeassistant/components/fibaro/translations/en.json index 2baeb3a7213..6bcff530798 100644 --- a/homeassistant/components/fibaro/translations/en.json +++ b/homeassistant/components/fibaro/translations/en.json @@ -11,9 +11,9 @@ "step": { "user": { "data": { - "url": "URL in the format http://HOST/api/", "import_plugins": "Import entities from fibaro plugins?", "password": "Password", + "url": "URL in the format http://HOST/api/", "username": "Username" } } diff --git a/homeassistant/components/fibaro/translations/et.json b/homeassistant/components/fibaro/translations/et.json new file mode 100644 index 00000000000..d9f140f8380 --- /dev/null +++ b/homeassistant/components/fibaro/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "import_plugins": "Kas importida olemid fibaro pistikprogrammidest?", + "password": "Salas\u00f5na", + "url": "URL vormingus http://HOST/api/", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/fr.json b/homeassistant/components/fibaro/translations/fr.json new file mode 100644 index 00000000000..8dee1529959 --- /dev/null +++ b/homeassistant/components/fibaro/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "import_plugins": "Importer les entit\u00e9s \u00e0 partir des plugins fibaro\u00a0?", + "password": "Mot de passe", + "url": "URL au format http://HOST/api/", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/he.json b/homeassistant/components/fibaro/translations/he.json new file mode 100644 index 00000000000..c479d8488f2 --- /dev/null +++ b/homeassistant/components/fibaro/translations/he.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/hu.json b/homeassistant/components/fibaro/translations/hu.json new file mode 100644 index 00000000000..d976d4b1a96 --- /dev/null +++ b/homeassistant/components/fibaro/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "import_plugins": "Import\u00e1ln\u00e1 az entit\u00e1sokat a fibaro be\u00e9p\u00fcl\u0151 modulokb\u00f3l?", + "password": "Jelsz\u00f3", + "url": "URL a k\u00f6vetkez\u0151 form\u00e1tumban: http://C\u00cdM/api/", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/id.json b/homeassistant/components/fibaro/translations/id.json new file mode 100644 index 00000000000..715ad91c275 --- /dev/null +++ b/homeassistant/components/fibaro/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "import_plugins": "Impor entitas dari plugin fibaro?", + "password": "Kata Sandi", + "url": "URL dalam format http://HOST/api/", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/it.json b/homeassistant/components/fibaro/translations/it.json new file mode 100644 index 00000000000..641ed94e49f --- /dev/null +++ b/homeassistant/components/fibaro/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "import_plugins": "Vuoi importare le entit\u00e0 dai plugin fibaro?", + "password": "Password", + "url": "URL nel formato http://HOST/api/", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/ja.json b/homeassistant/components/fibaro/translations/ja.json new file mode 100644 index 00000000000..8fc6562ff3b --- /dev/null +++ b/homeassistant/components/fibaro/translations/ja.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "import_plugins": "fibaro\u30d7\u30e9\u30b0\u30a4\u30f3\u304b\u3089\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u30a4\u30f3\u30dd\u30fc\u30c8\u3057\u307e\u3059\u304b\uff1f", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "url": "URL\u306e\u5f62\u5f0f\u306f\u3001http://HOST/api/ \u3067\u3059", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/nl.json b/homeassistant/components/fibaro/translations/nl.json new file mode 100644 index 00000000000..72b8588d1e4 --- /dev/null +++ b/homeassistant/components/fibaro/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kon niet verbinden", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "import_plugins": "Entiteiten importeren uit fibaro-plug-ins?", + "password": "Wachtwoord", + "url": "URL in het formaat http://HOST/api/", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/no.json b/homeassistant/components/fibaro/translations/no.json new file mode 100644 index 00000000000..8c868bb1ad8 --- /dev/null +++ b/homeassistant/components/fibaro/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "import_plugins": "Importere enheter fra fibaro plugins?", + "password": "Passord", + "url": "URL i formatet http://HOST/api/", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/pl.json b/homeassistant/components/fibaro/translations/pl.json new file mode 100644 index 00000000000..ce4b2652d80 --- /dev/null +++ b/homeassistant/components/fibaro/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "import_plugins": "Zaimportowa\u0107 encje z wtyczek fibaro?", + "password": "Has\u0142o", + "url": "Adres URL w formacie http://HOST/api/", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/pt-BR.json b/homeassistant/components/fibaro/translations/pt-BR.json new file mode 100644 index 00000000000..3e0f3139fa7 --- /dev/null +++ b/homeassistant/components/fibaro/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "import_plugins": "Importar entidades de plugins fibaro?", + "password": "Senha", + "url": "URL no formato http://HOST/api/", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/ru.json b/homeassistant/components/fibaro/translations/ru.json new file mode 100644 index 00000000000..56e75b5fa34 --- /dev/null +++ b/homeassistant/components/fibaro/translations/ru.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "import_plugins": "\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438\u0437 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 fibaro", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 http://HOST/api/", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/tr.json b/homeassistant/components/fibaro/translations/tr.json new file mode 100644 index 00000000000..c873e0dafd3 --- /dev/null +++ b/homeassistant/components/fibaro/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "import_plugins": "Varl\u0131klar\u0131 fibaro eklentilerinden i\u00e7e aktar\u0131ls\u0131n m\u0131?", + "password": "Parola", + "url": "http://HOST/api/ bi\u00e7imindeki URL", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fibaro/translations/zh-Hant.json b/homeassistant/components/fibaro/translations/zh-Hant.json new file mode 100644 index 00000000000..e494fb1012f --- /dev/null +++ b/homeassistant/components/fibaro/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "import_plugins": "\u5f9e fibaro \u5916\u639b\u532f\u5165\u5be6\u9ad4\uff1f", + "password": "\u5bc6\u78bc", + "url": "URL \u683c\u5f0f\u70ba http://HOST/api/", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/bg.json b/homeassistant/components/filesize/translations/bg.json new file mode 100644 index 00000000000..cfee062d421 --- /dev/null +++ b/homeassistant/components/filesize/translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430" + }, + "error": { + "not_allowed": "\u041f\u044a\u0442\u044f\u0442 \u043d\u0435 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d", + "not_valid": "\u041f\u044a\u0442\u044f\u0442 \u043d\u0435 \u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d" + }, + "step": { + "user": { + "data": { + "file_path": "\u041f\u044a\u0442 \u043a\u044a\u043c \u0444\u0430\u0439\u043b\u0430" + } + } + } + }, + "title": "\u0420\u0430\u0437\u043c\u0435\u0440 \u043d\u0430 \u0444\u0430\u0439\u043b\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/ca.json b/homeassistant/components/filesize/translations/ca.json new file mode 100644 index 00000000000..7bdcb4ae522 --- /dev/null +++ b/homeassistant/components/filesize/translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "El servei ja est\u00e0 configurat" + }, + "error": { + "not_allowed": "Ruta no permesa", + "not_valid": "Ruta inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "file_path": "Ruta al fitxer" + } + } + } + }, + "title": "Mida de fitxer" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/de.json b/homeassistant/components/filesize/translations/de.json new file mode 100644 index 00000000000..ac101d35783 --- /dev/null +++ b/homeassistant/components/filesize/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, + "error": { + "not_allowed": "Pfad nicht erlaubt", + "not_valid": "Pfad ung\u00fcltig" + }, + "step": { + "user": { + "data": { + "file_path": "Pfad zur Datei" + } + } + } + }, + "title": "Dateigr\u00f6\u00dfe" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/el.json b/homeassistant/components/filesize/translations/el.json new file mode 100644 index 00000000000..c1b8f4eab2c --- /dev/null +++ b/homeassistant/components/filesize/translations/el.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "not_allowed": "\u0397 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03b4\u03b5\u03bd \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9", + "not_valid": "\u0397 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7" + }, + "step": { + "user": { + "data": { + "file_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf" + } + } + } + }, + "title": "Filesize" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/en.json b/homeassistant/components/filesize/translations/en.json index cd8954e5a71..1bad0b9b081 100644 --- a/homeassistant/components/filesize/translations/en.json +++ b/homeassistant/components/filesize/translations/en.json @@ -1,19 +1,19 @@ { "config": { - "step": { - "user": { - "data": { - "file_path": "Path to file" - } + "abort": { + "already_configured": "Service is already configured" + }, + "error": { + "not_allowed": "Path is not allowed", + "not_valid": "Path is not valid" + }, + "step": { + "user": { + "data": { + "file_path": "Path to file" + } + } } - }, - "error": { - "not_valid": "Path is not valid", - "not_allowed": "Path is not allowed" - }, - "abort": { - "already_configured": "Filepath is already configured" - } }, "title": "Filesize" - } \ No newline at end of file +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/et.json b/homeassistant/components/filesize/translations/et.json new file mode 100644 index 00000000000..b27b482e4cd --- /dev/null +++ b/homeassistant/components/filesize/translations/et.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Teenus on juba h\u00e4\u00e4lestatud" + }, + "error": { + "not_allowed": "Kirje asukoht on keelatud", + "not_valid": "Kirjet ei leitud" + }, + "step": { + "user": { + "data": { + "file_path": "Kirje asukoht" + } + } + } + }, + "title": "Filesize" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/fr.json b/homeassistant/components/filesize/translations/fr.json new file mode 100644 index 00000000000..8f2f8ffe1ce --- /dev/null +++ b/homeassistant/components/filesize/translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "not_allowed": "Le chemin d'acc\u00e8s n'est pas autoris\u00e9", + "not_valid": "Le chemin d'acc\u00e8s n'est pas valide" + }, + "step": { + "user": { + "data": { + "file_path": "Chemin d'acc\u00e8s au fichier" + } + } + } + }, + "title": "Taille de fichier" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/he.json b/homeassistant/components/filesize/translations/he.json new file mode 100644 index 00000000000..48a6eeeea33 --- /dev/null +++ b/homeassistant/components/filesize/translations/he.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/hu.json b/homeassistant/components/filesize/translations/hu.json new file mode 100644 index 00000000000..74c9f598bb9 --- /dev/null +++ b/homeassistant/components/filesize/translations/hu.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "not_allowed": "Az el\u00e9r\u00e9si \u00fat nem enged\u00e9lyezett", + "not_valid": "Az el\u00e9r\u00e9si \u00fat \u00e9rv\u00e9nytelen" + }, + "step": { + "user": { + "data": { + "file_path": "A f\u00e1jl el\u00e9r\u00e9si \u00fatja" + } + } + } + }, + "title": "F\u00e1jlm\u00e9ret" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/id.json b/homeassistant/components/filesize/translations/id.json new file mode 100644 index 00000000000..cb4bfdebaa7 --- /dev/null +++ b/homeassistant/components/filesize/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "not_allowed": "Jalur tidak diperbolehkan", + "not_valid": "Jalur tidak valid" + }, + "step": { + "user": { + "data": { + "file_path": "Jalur ke file" + } + } + } + }, + "title": "Ukuran file" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/it.json b/homeassistant/components/filesize/translations/it.json new file mode 100644 index 00000000000..4372477b24c --- /dev/null +++ b/homeassistant/components/filesize/translations/it.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" + }, + "error": { + "not_allowed": "Il percorso non \u00e8 consentito", + "not_valid": "Il percorso non \u00e8 valido" + }, + "step": { + "user": { + "data": { + "file_path": "Percorso del file" + } + } + } + }, + "title": "Dimensione file" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/ja.json b/homeassistant/components/filesize/translations/ja.json new file mode 100644 index 00000000000..3d8ca72452b --- /dev/null +++ b/homeassistant/components/filesize/translations/ja.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "not_allowed": "\u30d1\u30b9\u304c\u8a31\u53ef\u3055\u308c\u3066\u3044\u307e\u305b\u3093", + "not_valid": "\u30d1\u30b9\u304c\u7121\u52b9\u3067\u3059" + }, + "step": { + "user": { + "data": { + "file_path": "\u30d5\u30a1\u30a4\u30eb\u3078\u306e\u30d1\u30b9" + } + } + } + }, + "title": "\u30d5\u30a1\u30a4\u30eb\u30b5\u30a4\u30ba" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/nl.json b/homeassistant/components/filesize/translations/nl.json new file mode 100644 index 00000000000..d8a35446044 --- /dev/null +++ b/homeassistant/components/filesize/translations/nl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Service is al geconfigureerd" + }, + "error": { + "not_allowed": "Pad is niet toegestaan", + "not_valid": "Pad is niet geldig" + }, + "step": { + "user": { + "data": { + "file_path": "Pad naar bestand" + } + } + } + }, + "title": "Bestandsgrootte" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/no.json b/homeassistant/components/filesize/translations/no.json new file mode 100644 index 00000000000..18bdf37d667 --- /dev/null +++ b/homeassistant/components/filesize/translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert" + }, + "error": { + "not_allowed": "Stien er ikke tillatt", + "not_valid": "Banen er ikke gyldig" + }, + "step": { + "user": { + "data": { + "file_path": "Bane til fil" + } + } + } + }, + "title": "Filst\u00f8rrelse" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/pl.json b/homeassistant/components/filesize/translations/pl.json new file mode 100644 index 00000000000..2b2cd21a79c --- /dev/null +++ b/homeassistant/components/filesize/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" + }, + "error": { + "not_allowed": "Niedozwolona \u015bcie\u017cka", + "not_valid": "Nieprawid\u0142owa \u015bcie\u017cka" + }, + "step": { + "user": { + "data": { + "file_path": "\u015acie\u017cka do pliku" + } + } + } + }, + "title": "Rozmiar pliku" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/pt-BR.json b/homeassistant/components/filesize/translations/pt-BR.json new file mode 100644 index 00000000000..dfcc9cc5348 --- /dev/null +++ b/homeassistant/components/filesize/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "error": { + "not_allowed": "O caminho n\u00e3o \u00e9 permitido", + "not_valid": "O caminho n\u00e3o \u00e9 v\u00e1lido" + }, + "step": { + "user": { + "data": { + "file_path": "Caminho para o arquivo" + } + } + } + }, + "title": "Tamanho do arquivo" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/ru.json b/homeassistant/components/filesize/translations/ru.json new file mode 100644 index 00000000000..1071fd09ca5 --- /dev/null +++ b/homeassistant/components/filesize/translations/ru.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "not_allowed": "\u041f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u043d\u0443\u0436\u043d\u044b\u0445 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439.", + "not_valid": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443." + }, + "step": { + "user": { + "data": { + "file_path": "\u041f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443" + } + } + } + }, + "title": "\u0420\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/tr.json b/homeassistant/components/filesize/translations/tr.json new file mode 100644 index 00000000000..cf62d56284e --- /dev/null +++ b/homeassistant/components/filesize/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "not_allowed": "Yola izin verilmiyor", + "not_valid": "Yol ge\u00e7erli de\u011fil" + }, + "step": { + "user": { + "data": { + "file_path": "Dosya yolu" + } + } + } + }, + "title": "Dosya boyutu" +} \ No newline at end of file diff --git a/homeassistant/components/filesize/translations/zh-Hant.json b/homeassistant/components/filesize/translations/zh-Hant.json new file mode 100644 index 00000000000..692d770d337 --- /dev/null +++ b/homeassistant/components/filesize/translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "not_allowed": "\u8def\u5f91\u4e0d\u5141\u8a31", + "not_valid": "\u8def\u5f91\u7121\u6548" + }, + "step": { + "user": { + "data": { + "file_path": "\u6a94\u6848\u8def\u5f91" + } + } + } + }, + "title": "\u6a94\u6848\u5927\u5c0f" +} \ No newline at end of file diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 59ba706577c..46a3c0ec963 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -19,6 +19,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASSES as SENSOR_DEVICE_CLASSES, DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA, + STATE_CLASSES as SENSOR_STATE_CLASSES, SensorDeviceClass, SensorEntity, ) @@ -262,7 +263,10 @@ class SensorFilter(SensorEntity): ): self._device_class = new_state.attributes.get(ATTR_DEVICE_CLASS) - if self._attr_state_class is None: + if ( + self._attr_state_class is None + and new_state.attributes.get(ATTR_STATE_CLASS) in SENSOR_STATE_CLASSES + ): self._attr_state_class = new_state.attributes.get(ATTR_STATE_CLASS) if self._unit_of_measurement is None: diff --git a/homeassistant/components/firmata/light.py b/homeassistant/components/firmata/light.py index de511058114..8f562b12d30 100644 --- a/homeassistant/components/firmata/light.py +++ b/homeassistant/components/firmata/light.py @@ -3,11 +3,7 @@ from __future__ import annotations import logging -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MAXIMUM, CONF_MINIMUM, CONF_NAME, CONF_PIN from homeassistant.core import HomeAssistant @@ -56,6 +52,9 @@ async def async_setup_entry( class FirmataLight(FirmataPinEntity, LightEntity): """Representation of a light on a Firmata board.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__( self, api: FirmataBoardPin, @@ -83,11 +82,6 @@ class FirmataLight(FirmataPinEntity, LightEntity): """Return the brightness of the light.""" return self._api.state - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" level = kwargs.get(ATTR_BRIGHTNESS, self._last_on_level) diff --git a/homeassistant/components/fivem/translations/hu.json b/homeassistant/components/fivem/translations/hu.json index b307c6e79fc..4e56dc3c17f 100644 --- a/homeassistant/components/fivem/translations/hu.json +++ b/homeassistant/components/fivem/translations/hu.json @@ -13,7 +13,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "port": "Port" } } diff --git a/homeassistant/components/fjaraskupan/fan.py b/homeassistant/components/fjaraskupan/fan.py index 4b04910a167..a8f8e13f3da 100644 --- a/homeassistant/components/fjaraskupan/fan.py +++ b/homeassistant/components/fjaraskupan/fan.py @@ -10,11 +10,7 @@ from fjaraskupan import ( State, ) -from homeassistant.components.fan import ( - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -73,6 +69,8 @@ async def async_setup_entry( class Fan(CoordinatorEntity[DataUpdateCoordinator[State]], FanEntity): """Fan entity.""" + _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + def __init__( self, coordinator: DataUpdateCoordinator[State], @@ -155,11 +153,6 @@ class Fan(CoordinatorEntity[DataUpdateCoordinator[State]], FanEntity): """Return the current speed.""" return self._percentage - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE - @property def is_on(self) -> bool: """Return true if fan is on.""" diff --git a/homeassistant/components/fjaraskupan/light.py b/homeassistant/components/fjaraskupan/light.py index 7c1e5d34138..6c3e6e458f8 100644 --- a/homeassistant/components/fjaraskupan/light.py +++ b/homeassistant/components/fjaraskupan/light.py @@ -3,11 +3,7 @@ from __future__ import annotations from fjaraskupan import COMMAND_LIGHT_ON_OFF, Device, State -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - COLOR_MODE_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo, Entity @@ -49,8 +45,8 @@ class Light(CoordinatorEntity[DataUpdateCoordinator[State]], LightEntity): """Init light entity.""" super().__init__(coordinator) self._device = device - self._attr_color_mode = COLOR_MODE_BRIGHTNESS - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} self._attr_unique_id = device.address self._attr_device_info = device_info self._attr_name = device_info["name"] diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index 9e2b9e41255..c24acf78221 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -6,11 +6,7 @@ import logging import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.components.modbus import get_hub from homeassistant.components.modbus.const import ( CALL_TYPE_REGISTER_HOLDING, @@ -42,8 +38,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( _LOGGER = logging.getLogger(__name__) -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - async def async_setup_platform( hass: HomeAssistant, @@ -61,6 +55,10 @@ async def async_setup_platform( class Flexit(ClimateEntity): """Representation of a Flexit AC unit.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) + def __init__( self, hub: ModbusHub, modbus_slave: int | None, name: str | None ) -> None: @@ -83,11 +81,6 @@ class Flexit(ClimateEntity): self._alarm = False self._outdoor_air_temp = None - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - async def async_update(self): """Update unit attributes.""" self._target_temperature = await self._async_read_temp_from_register( @@ -190,7 +183,7 @@ class Flexit(ClimateEntity): Need to be a subset of HVAC_MODES. """ - return [HVAC_MODE_COOL] + return [HVACMode.COOL] @property def fan_mode(self): diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index 92bc81b5aa0..94638695b23 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -7,7 +7,12 @@ from pyflick import FlickAPI, FlickPrice from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME +from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_FRIENDLY_NAME, + CURRENCY_CENT, + ENERGY_KILO_WATT_HOUR, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.dt import utcnow @@ -20,7 +25,6 @@ SCAN_INTERVAL = timedelta(minutes=5) ATTRIBUTION = "Data provided by Flick Electric" FRIENDLY_NAME = "Flick Power Price" -UNIT_NAME = "cents" async def async_setup_entry( @@ -35,7 +39,7 @@ async def async_setup_entry( class FlickPricingSensor(SensorEntity): """Entity object for Flick Electric sensor.""" - _attr_native_unit_of_measurement = UNIT_NAME + _attr_native_unit_of_measurement = f"{CURRENCY_CENT}/{ENERGY_KILO_WATT_HOUR}" def __init__(self, api: FlickAPI) -> None: """Entity object for Flick Electric sensor.""" diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index 04766c0196a..a5d54386633 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -1,7 +1,11 @@ """Support for Flo Water Monitor sensors.""" from __future__ import annotations -from homeassistant.components.sensor import SensorDeviceClass, SensorEntity +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorStateClass, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( PERCENTAGE, @@ -65,6 +69,7 @@ class FloDailyUsageSensor(FloEntity, SensorEntity): _attr_icon = WATER_ICON _attr_native_unit_of_measurement = VOLUME_GALLONS + _attr_state_class: SensorStateClass = SensorStateClass.TOTAL_INCREASING def __init__(self, device): """Initialize the daily water usage sensor.""" @@ -100,6 +105,7 @@ class FloCurrentFlowRateSensor(FloEntity, SensorEntity): _attr_icon = GAUGE_ICON _attr_native_unit_of_measurement = "gpm" + _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT def __init__(self, device): """Initialize the flow rate sensor.""" @@ -119,6 +125,7 @@ class FloTemperatureSensor(FloEntity, SensorEntity): _attr_device_class = SensorDeviceClass.TEMPERATURE _attr_native_unit_of_measurement = TEMP_FAHRENHEIT + _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT def __init__(self, name, device): """Initialize the temperature sensor.""" @@ -138,6 +145,7 @@ class FloHumiditySensor(FloEntity, SensorEntity): _attr_device_class = SensorDeviceClass.HUMIDITY _attr_native_unit_of_measurement = PERCENTAGE + _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT def __init__(self, device): """Initialize the humidity sensor.""" @@ -157,6 +165,7 @@ class FloPressureSensor(FloEntity, SensorEntity): _attr_device_class = SensorDeviceClass.PRESSURE _attr_native_unit_of_measurement = PRESSURE_PSI + _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT def __init__(self, device): """Initialize the pressure sensor.""" @@ -176,6 +185,7 @@ class FloBatterySensor(FloEntity, SensorEntity): _attr_device_class = SensorDeviceClass.BATTERY _attr_native_unit_of_measurement = PERCENTAGE + _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT def __init__(self, device): """Initialize the battery sensor.""" diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index d0120e1979f..01ab2c9259e 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -51,7 +51,10 @@ async def async_setup_entry( platform.async_register_entity_service( SERVICE_SET_SLEEP_MODE, { - vol.Required(ATTR_SLEEP_MINUTES, default=120): vol.In(SLEEP_MINUTE_OPTIONS), + vol.Required(ATTR_SLEEP_MINUTES, default=120): vol.All( + vol.Coerce(int), + vol.In(SLEEP_MINUTE_OPTIONS), + ), vol.Required(ATTR_REVERT_TO_MODE, default=SYSTEM_MODE_HOME): vol.In( SYSTEM_REVERT_MODES ), diff --git a/homeassistant/components/flock/manifest.json b/homeassistant/components/flock/manifest.json index ddbb2bb201c..abcaee53a70 100644 --- a/homeassistant/components/flock/manifest.json +++ b/homeassistant/components/flock/manifest.json @@ -2,6 +2,6 @@ "domain": "flock", "name": "Flock", "documentation": "https://www.home-assistant.io/integrations/flock", - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_push" } diff --git a/homeassistant/components/flux_led/const.py b/homeassistant/components/flux_led/const.py index 9124be5bb9e..7fa841ec77f 100644 --- a/homeassistant/components/flux_led/const.py +++ b/homeassistant/components/flux_led/const.py @@ -11,12 +11,7 @@ from flux_led.const import ( COLOR_MODE_RGBWW as FLUX_COLOR_MODE_RGBWW, ) -from homeassistant.components.light import ( - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, -) +from homeassistant.components.light import ColorMode DOMAIN: Final = "flux_led" @@ -24,13 +19,13 @@ MIN_RGB_BRIGHTNESS: Final = 1 MIN_CCT_BRIGHTNESS: Final = 2 FLUX_COLOR_MODE_TO_HASS: Final = { - FLUX_COLOR_MODE_RGB: COLOR_MODE_RGB, - FLUX_COLOR_MODE_RGBW: COLOR_MODE_RGBW, - FLUX_COLOR_MODE_RGBWW: COLOR_MODE_RGBWW, - FLUX_COLOR_MODE_CCT: COLOR_MODE_COLOR_TEMP, + FLUX_COLOR_MODE_RGB: ColorMode.RGB, + FLUX_COLOR_MODE_RGBW: ColorMode.RGBW, + FLUX_COLOR_MODE_RGBWW: ColorMode.RGBWW, + FLUX_COLOR_MODE_CCT: ColorMode.COLOR_TEMP, } -MULTI_BRIGHTNESS_COLOR_MODES: Final = {COLOR_MODE_RGBWW, COLOR_MODE_RGBW} +MULTI_BRIGHTNESS_COLOR_MODES: Final = {ColorMode.RGBWW, ColorMode.RGBW} API: Final = "flux_api" @@ -73,7 +68,7 @@ CONF_TRANSITION: Final = "transition" CONF_EFFECT: Final = "effect" -EFFECT_SPEED_SUPPORT_MODES: Final = {COLOR_MODE_RGB, COLOR_MODE_RGBW, COLOR_MODE_RGBWW} +EFFECT_SPEED_SUPPORT_MODES: Final = {ColorMode.RGB, ColorMode.RGBW, ColorMode.RGBWW} CONF_CUSTOM_EFFECT_COLORS: Final = "custom_effect_colors" diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 2942a13c734..202f0f95e23 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -19,9 +19,8 @@ from homeassistant.components.light import ( ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, ATTR_WHITE, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback @@ -192,7 +191,7 @@ class FluxLight( ): """Representation of a Flux light.""" - _attr_supported_features = SUPPORT_TRANSITION | SUPPORT_EFFECT + _attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT def __init__( self, diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index cbd5e51b163..e28b55869c7 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.27"], + "requirements": ["flux_led==0.28.28"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", @@ -38,6 +38,9 @@ "macaddress": "8CCE4E*", "hostname": "lwip*" }, + { + "hostname": "hf-lpb100-zj*" + }, { "hostname": "zengge_[0-9a-f][0-9a-f]_*" }, diff --git a/homeassistant/components/flux_led/translations/hu.json b/homeassistant/components/flux_led/translations/hu.json index 1208f87fe70..f2f5d9b8751 100644 --- a/homeassistant/components/flux_led/translations/hu.json +++ b/homeassistant/components/flux_led/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { diff --git a/homeassistant/components/flux_led/util.py b/homeassistant/components/flux_led/util.py index 9adbacb4273..6bcf4538dfb 100644 --- a/homeassistant/components/flux_led/util.py +++ b/homeassistant/components/flux_led/util.py @@ -4,11 +4,7 @@ from __future__ import annotations from flux_led.aio import AIOWifiLedBulb from flux_led.const import COLOR_MODE_DIM as FLUX_COLOR_MODE_DIM, MultiColorEffects -from homeassistant.components.light import ( - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_ONOFF, - COLOR_MODE_WHITE, -) +from homeassistant.components.light import ColorMode from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv from .const import FLUX_COLOR_MODE_TO_HASS, MIN_RGB_BRIGHTNESS @@ -42,15 +38,15 @@ def mac_matches_by_one(formatted_mac_1: str, formatted_mac_2: str) -> bool: def _flux_color_mode_to_hass( flux_color_mode: str | None, flux_color_modes: set[str] -) -> str: +) -> ColorMode: """Map the flux color mode to Home Assistant color mode.""" if flux_color_mode is None: - return COLOR_MODE_ONOFF + return ColorMode.ONOFF if flux_color_mode == FLUX_COLOR_MODE_DIM: if len(flux_color_modes) > 1: - return COLOR_MODE_WHITE - return COLOR_MODE_BRIGHTNESS - return FLUX_COLOR_MODE_TO_HASS.get(flux_color_mode, COLOR_MODE_ONOFF) + return ColorMode.WHITE + return ColorMode.BRIGHTNESS + return FLUX_COLOR_MODE_TO_HASS.get(flux_color_mode, ColorMode.ONOFF) def _effect_brightness(brightness: int) -> int: diff --git a/homeassistant/components/folder_watcher/manifest.json b/homeassistant/components/folder_watcher/manifest.json index fb9a9ea5d63..918492c82e9 100644 --- a/homeassistant/components/folder_watcher/manifest.json +++ b/homeassistant/components/folder_watcher/manifest.json @@ -2,7 +2,7 @@ "domain": "folder_watcher", "name": "Folder Watcher", "documentation": "https://www.home-assistant.io/integrations/folder_watcher", - "requirements": ["watchdog==2.1.6"], + "requirements": ["watchdog==2.1.7"], "codeowners": [], "quality_scale": "internal", "iot_class": "local_polling", diff --git a/homeassistant/components/forecast_solar/translations/ca.json b/homeassistant/components/forecast_solar/translations/ca.json index 7bd31828080..141ad11c165 100644 --- a/homeassistant/components/forecast_solar/translations/ca.json +++ b/homeassistant/components/forecast_solar/translations/ca.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 graus, 0 = nord, 90 = est, 180 = sud, 270 = oest)", "damping": "Factor d'amortiment: ajusta els resultats al mat\u00ed i al vespre", "declination": "Inclinaci\u00f3 (0 = horitzontal, 90 = vertical)", + "inverter_size": "Pot\u00e8ncia de l'inversor (Watts)", "modules power": "Pot\u00e8ncia m\u00e0xima total dels panells solars" }, "description": "Aquests valors permeten ajustar els resultats de Solar.Forecast. Consulta la documentaci\u00f3 si tens dubtes sobre algun camp." diff --git a/homeassistant/components/forecast_solar/translations/de.json b/homeassistant/components/forecast_solar/translations/de.json index 43b60424cf1..06e51e04659 100644 --- a/homeassistant/components/forecast_solar/translations/de.json +++ b/homeassistant/components/forecast_solar/translations/de.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 Grad, 0 = Norden, 90 = Osten, 180 = S\u00fcden, 270 = Westen)", "damping": "D\u00e4mpfungsfaktor: passt die Ergebnisse morgens und abends an", "declination": "Deklination (0 = Horizontal, 90 = Vertikal)", + "inverter_size": "Wechselrichtergr\u00f6\u00dfe (Watt)", "modules power": "Gesamt-Watt-Spitzenleistung deiner Solarmodule" }, "description": "Mit diesen Werten kann das Solar.Forecast-Ergebnis angepasst werden. Wenn ein Feld unklar ist, lies bitte in der Dokumentation nach." diff --git a/homeassistant/components/forecast_solar/translations/el.json b/homeassistant/components/forecast_solar/translations/el.json index 0f6603a622b..e2e75c05f65 100644 --- a/homeassistant/components/forecast_solar/translations/el.json +++ b/homeassistant/components/forecast_solar/translations/el.json @@ -22,6 +22,7 @@ "azimuth": "\u0391\u03b6\u03b9\u03bc\u03bf\u03cd\u03b8\u03b9\u03bf (360 \u03bc\u03bf\u03af\u03c1\u03b5\u03c2, 0 = \u0392\u03bf\u03c1\u03c1\u03ac\u03c2, 90 = \u0391\u03bd\u03b1\u03c4\u03bf\u03bb\u03ae, 180 = \u039d\u03cc\u03c4\u03bf\u03c2, 270 = \u0394\u03cd\u03c3\u03b7)", "damping": "\u03a3\u03c5\u03bd\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03ae\u03c2 \u03b1\u03c0\u03cc\u03c3\u03b2\u03b5\u03c3\u03b7\u03c2: \u03c1\u03c5\u03b8\u03bc\u03af\u03b6\u03b5\u03b9 \u03c4\u03b1 \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03c0\u03c1\u03c9\u03af \u03ba\u03b1\u03b9 \u03b2\u03c1\u03ac\u03b4\u03c5", "declination": "\u0391\u03c0\u03cc\u03ba\u03bb\u03b9\u03c3\u03b7 (0 = \u03bf\u03c1\u03b9\u03b6\u03cc\u03bd\u03c4\u03b9\u03b1, 90 = \u03ba\u03b1\u03c4\u03b1\u03ba\u03cc\u03c1\u03c5\u03c6\u03b7)", + "inverter_size": "\u039c\u03ad\u03b3\u03b5\u03b8\u03bf\u03c2 \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1 (Watt)", "modules power": "\u03a3\u03c5\u03bd\u03bf\u03bb\u03b9\u03ba\u03ae \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03b7 \u03b9\u03c3\u03c7\u03cd\u03c2 Watt \u03c4\u03c9\u03bd \u03b7\u03bb\u03b9\u03b1\u03ba\u03ce\u03bd \u03c3\u03b1\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03c9\u03bd" }, "description": "\u0391\u03c5\u03c4\u03ad\u03c2 \u03bf\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03c5\u03bd \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2 Solar.Forecast. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b5\u03ac\u03bd \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf \u03c0\u03b5\u03b4\u03af\u03bf \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03b1\u03c6\u03ad\u03c2." diff --git a/homeassistant/components/forecast_solar/translations/en.json b/homeassistant/components/forecast_solar/translations/en.json index db9bead2e8c..2aa5a37cd1c 100644 --- a/homeassistant/components/forecast_solar/translations/en.json +++ b/homeassistant/components/forecast_solar/translations/en.json @@ -21,8 +21,8 @@ "api_key": "Forecast.Solar API Key (optional)", "azimuth": "Azimuth (360 degrees, 0 = North, 90 = East, 180 = South, 270 = West)", "damping": "Damping factor: adjusts the results in the morning and evening", - "inverter_size": "Inverter size (Watt)", "declination": "Declination (0 = Horizontal, 90 = Vertical)", + "inverter_size": "Inverter size (Watt)", "modules power": "Total Watt peak power of your solar modules" }, "description": "These values allow tweaking the Solar.Forecast result. Please refer to the documentation if a field is unclear." diff --git a/homeassistant/components/forecast_solar/translations/et.json b/homeassistant/components/forecast_solar/translations/et.json index 7aa87f4cf58..d2b72b30708 100644 --- a/homeassistant/components/forecast_solar/translations/et.json +++ b/homeassistant/components/forecast_solar/translations/et.json @@ -22,6 +22,7 @@ "azimuth": "Asimuut (360 kraadi, 0 = p\u00f5hi, 90 = ida, 180 = l\u00f5una, 270 = l\u00e4\u00e4s)", "damping": "Summutustegur: reguleerib tulemusi hommikul ja \u00f5htul", "declination": "Deklinatsioon (0 = horisontaalne, 90 = vertikaalne)", + "inverter_size": "Inverteri v\u00f5imsus (vatti)", "modules power": "P\u00e4ikesemoodulite koguv\u00f5imsus vattides" }, "description": "Need v\u00e4\u00e4rtused v\u00f5imaldavad muuta Solar.Forecast tulemust. Vaata dokumentatsiooni kui asi on ebaselge." diff --git a/homeassistant/components/forecast_solar/translations/fr.json b/homeassistant/components/forecast_solar/translations/fr.json index efd9f7be3a6..78b4d3f8f88 100644 --- a/homeassistant/components/forecast_solar/translations/fr.json +++ b/homeassistant/components/forecast_solar/translations/fr.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 degr\u00e9s, 0 = Nord, 90 = Est, 180 = Sud, 270 = Ouest)", "damping": "Facteur d'amortissement : ajuste les r\u00e9sultats matin et soir", "declination": "D\u00e9clinaison (0 = horizontale, 90 = verticale)", + "inverter_size": "Taille de l'onduleur (en watts)", "modules power": "Puissance de cr\u00eate totale en watts de vos modules solaires" }, "description": "Ces valeurs permettent de peaufiner le r\u00e9sultat Solar.Forecast. Veuillez vous r\u00e9f\u00e9rer \u00e0 la documentation si un champ n'est pas clair." diff --git a/homeassistant/components/forecast_solar/translations/hu.json b/homeassistant/components/forecast_solar/translations/hu.json index 0bd814f16be..33a69ad2fd7 100644 --- a/homeassistant/components/forecast_solar/translations/hu.json +++ b/homeassistant/components/forecast_solar/translations/hu.json @@ -8,7 +8,7 @@ "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", "modules power": "A napelemmodulok teljes cs\u00facsteljes\u00edtm\u00e9nye (Watt)", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "T\u00f6ltse ki a napelemek adatait. K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t, ha egy mez\u0151 nem egy\u00e9rtelm\u0171." } @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 fok, 0 = \u00e9szak, 90 = keleti, 180 = d\u00e9li, 270 = nyugati)", "damping": "Csillap\u00edt\u00e1si t\u00e9nyez\u0151: be\u00e1ll\u00edtja az eredm\u00e9nyeket reggelre \u00e9s est\u00e9re", "declination": "Deklin\u00e1ci\u00f3 (0 = v\u00edzszintes, 90 = f\u00fcgg\u0151leges)", + "inverter_size": "Inverter m\u00e9rete (Watt)", "modules power": "A napelemmodulok teljes cs\u00facsteljes\u00edtm\u00e9nye (Watt)" }, "description": "Ezek az \u00e9rt\u00e9kek lehet\u0151v\u00e9 teszik a Solar.Forecast eredm\u00e9ny m\u00f3dos\u00edt\u00e1s\u00e1t. K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t, ha egy mez\u0151 nem egy\u00e9rtelm\u0171." diff --git a/homeassistant/components/forecast_solar/translations/id.json b/homeassistant/components/forecast_solar/translations/id.json index 27ef16e0266..5bd1236d6a6 100644 --- a/homeassistant/components/forecast_solar/translations/id.json +++ b/homeassistant/components/forecast_solar/translations/id.json @@ -22,6 +22,7 @@ "azimuth": "Azimuth (360 derajat, 0 = Utara, 90 = Timur, 180 = Selatan, 270 = Barat)", "damping": "Faktor redaman: menyesuaikan hasil di pagi dan sore hari", "declination": "Deklinasi (0 = Horizontal, 90 = Vertikal)", + "inverter_size": "Ukuran inverter (Watt)", "modules power": "Total daya puncak modul surya Anda dalam Watt" }, "description": "Nilai-nilai ini memungkinkan penyesuaian hasil Solar.Forecast. Rujuk ke dokumentasi jika bidang isian tidak jelas." diff --git a/homeassistant/components/forecast_solar/translations/it.json b/homeassistant/components/forecast_solar/translations/it.json index 7920eee43eb..598c67695cc 100644 --- a/homeassistant/components/forecast_solar/translations/it.json +++ b/homeassistant/components/forecast_solar/translations/it.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 gradi, 0 = Nord, 90 = Est, 180 = Sud, 270 = Ovest)", "damping": "Fattore di smorzamento: regola i risultati al mattino e alla sera", "declination": "Declinazione (0 = Orizzontale, 90 = Verticale)", + "inverter_size": "Dimensioni inverter (Watt)", "modules power": "Potenza di picco totale in Watt dei tuoi moduli solari" }, "description": "Questi valori consentono di modificare il risultato di Solar.Forecast. Fai riferimento alla documentazione se un campo non \u00e8 chiaro." diff --git a/homeassistant/components/forecast_solar/translations/ja.json b/homeassistant/components/forecast_solar/translations/ja.json index 62090376bed..d86dc08f2b7 100644 --- a/homeassistant/components/forecast_solar/translations/ja.json +++ b/homeassistant/components/forecast_solar/translations/ja.json @@ -22,6 +22,7 @@ "azimuth": "\u65b9\u4f4d\u89d2(360\u5ea6\u30010=\u5317\u300190=\u6771\u3001180=\u5357\u3001270=\u897f)", "damping": "\u6e1b\u8870\u4fc2\u6570(\u30c0\u30f3\u30d4\u30f3\u30b0\u30d5\u30a1\u30af\u30bf\u30fc): \u671d\u3068\u5915\u65b9\u306e\u7d50\u679c\u3092\u8abf\u6574\u3059\u308b", "declination": "\u504f\u89d2(0\uff1d\u6c34\u5e73\u300190\uff1d\u5782\u76f4)", + "inverter_size": "\u30a4\u30f3\u30d0\u30fc\u30bf\u30fc\u306e\u30b5\u30a4\u30ba\uff08\u30ef\u30c3\u30c8\uff09", "modules power": "\u30bd\u30fc\u30e9\u30fc\u30e2\u30b8\u30e5\u30fc\u30eb\u306e\u7dcf\u30ef\u30c3\u30c8\u30d4\u30fc\u30af\u96fb\u529b" }, "description": "\u3053\u308c\u3089\u306e\u5024\u306b\u3088\u308a\u3001Solar.Forecast\u306e\u7d50\u679c\u3092\u5fae\u8abf\u6574\u3067\u304d\u307e\u3059\u3002\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u4e0d\u660e\u306a\u5834\u5408\u306f\u3001\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" diff --git a/homeassistant/components/forecast_solar/translations/nl.json b/homeassistant/components/forecast_solar/translations/nl.json index c66d272782d..dbc966e59fc 100644 --- a/homeassistant/components/forecast_solar/translations/nl.json +++ b/homeassistant/components/forecast_solar/translations/nl.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 graden, 0 = Noord, 90 = Oost, 180 = Zuid, 270 = West)", "damping": "Dempingsfactor: past de resultaten 's ochtends en 's avonds aan", "declination": "Declinatie (0 = Horizontaal, 90 = Verticaal)", + "inverter_size": "Omvormer grootte (Watt)", "modules power": "Totaal Watt piekvermogen van uw zonnepanelen" }, "description": "Met deze waarden kan het resultaat van Solar.Forecast worden aangepast. Raadpleeg de documentatie als een veld onduidelijk is." diff --git a/homeassistant/components/forecast_solar/translations/no.json b/homeassistant/components/forecast_solar/translations/no.json index 1504727c1ae..a9acbb86f00 100644 --- a/homeassistant/components/forecast_solar/translations/no.json +++ b/homeassistant/components/forecast_solar/translations/no.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 grader, 0 = Nord, 90 = \u00d8st, 180 = S\u00f8r, 270 = Vest)", "damping": "Dempingsfaktor: justerer resultatene om morgenen og kvelden", "declination": "Deklinasjon (0 = horisontal, 90 = vertikal)", + "inverter_size": "Inverterst\u00f8rrelse (Watt)", "modules power": "Total Watt-toppeffekt i solcellemodulene dine" }, "description": "Disse verdiene tillater justering av Solar.Forecast -resultatet. Se dokumentasjonen hvis et felt er uklart." diff --git a/homeassistant/components/forecast_solar/translations/pl.json b/homeassistant/components/forecast_solar/translations/pl.json index 3fc782fe7c3..ad01ce4bb54 100644 --- a/homeassistant/components/forecast_solar/translations/pl.json +++ b/homeassistant/components/forecast_solar/translations/pl.json @@ -22,6 +22,7 @@ "azimuth": "Azymut (360 stopni, 0 = P\u00f3\u0142noc, 90 = Wsch\u00f3d, 180 = Po\u0142udnie, 270 = Zach\u00f3d)", "damping": "Wsp\u00f3\u0142czynnik t\u0142umienia: dostosowuje wyniki rano i wieczorem", "declination": "Deklinacja (0 = Poziomo, 90 = Pionowo)", + "inverter_size": "Rozmiar falownika (Wat)", "modules power": "Ca\u0142kowita moc szczytowa modu\u0142\u00f3w fotowoltaicznych w watach" }, "description": "Te warto\u015bci pozwalaj\u0105 dostosowa\u0107 wyniki dla Solar.Forecast. Prosz\u0119 zapozna\u0107 si\u0119 z dokumentacj\u0105, je\u015bli pole jest niejasne." diff --git a/homeassistant/components/forecast_solar/translations/pt-BR.json b/homeassistant/components/forecast_solar/translations/pt-BR.json index ad6cca066c4..6761e17e8bd 100644 --- a/homeassistant/components/forecast_solar/translations/pt-BR.json +++ b/homeassistant/components/forecast_solar/translations/pt-BR.json @@ -22,6 +22,7 @@ "azimuth": "Azimute (360\u00b0, 0\u00b0 = Norte, 90\u00b0 = Leste, 180\u00b0 = Sul, 270\u00b0 = Oeste)", "damping": "Fator de amortecimento: ajusta os resultados de manh\u00e3 e \u00e0 noite", "declination": "Declina\u00e7\u00e3o (0\u00b0 = Horizontal, 90\u00b0 = Vertical)", + "inverter_size": "Pot\u00eancia do inversor (Watt)", "modules power": "Pot\u00eancia de pico total em Watt de seus m\u00f3dulos solares" }, "description": "Preencha os dados de seus pain\u00e9is solares. Consulte a documenta\u00e7\u00e3o se um campo n\u00e3o estiver claro." diff --git a/homeassistant/components/forecast_solar/translations/ru.json b/homeassistant/components/forecast_solar/translations/ru.json index 9cf8e87a8e2..f7d4d502691 100644 --- a/homeassistant/components/forecast_solar/translations/ru.json +++ b/homeassistant/components/forecast_solar/translations/ru.json @@ -22,6 +22,7 @@ "azimuth": "\u0410\u0437\u0438\u043c\u0443\u0442 (360 \u0433\u0440\u0430\u0434\u0443\u0441\u043e\u0432, 0 = \u0441\u0435\u0432\u0435\u0440, 90 = \u0432\u043e\u0441\u0442\u043e\u043a, 180 = \u044e\u0433, 270 = \u0437\u0430\u043f\u0430\u0434)", "damping": "\u0424\u0430\u043a\u0442\u043e\u0440 \u0437\u0430\u0442\u0443\u0445\u0430\u043d\u0438\u044f: \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0443\u0442\u0440\u043e\u043c \u0438 \u0432\u0435\u0447\u0435\u0440\u043e\u043c", "declination": "\u0421\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u0435 (0 = \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435, 90 = \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435)", + "inverter_size": "\u041c\u043e\u0449\u043d\u043e\u0441\u0442\u044c \u0438\u043d\u0432\u0435\u0440\u0442\u043e\u0440\u0430 (\u0432 \u0412\u0430\u0442\u0442\u0430\u0445)", "modules power": "\u041e\u0431\u0449\u0430\u044f \u043f\u0438\u043a\u043e\u0432\u0430\u044f \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u044c \u0412\u0430\u0448\u0438\u0445 \u0441\u043e\u043b\u043d\u0435\u0447\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439 (\u0432 \u0412\u0430\u0442\u0442\u0430\u0445)" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Forecast.Solar." diff --git a/homeassistant/components/forecast_solar/translations/tr.json b/homeassistant/components/forecast_solar/translations/tr.json index fecd8d7889a..1fa6def5714 100644 --- a/homeassistant/components/forecast_solar/translations/tr.json +++ b/homeassistant/components/forecast_solar/translations/tr.json @@ -22,6 +22,7 @@ "azimuth": "Azimut (360 derece, 0 = Kuzey, 90 = Do\u011fu, 180 = G\u00fcney, 270 = Bat\u0131)", "damping": "S\u00f6n\u00fcmleme fakt\u00f6r\u00fc: sonu\u00e7lar\u0131 sabah ve ak\u015fam ayarlar", "declination": "Sapma (0 = Yatay, 90 = Dikey)", + "inverter_size": "\u0130nverter boyutu (Watt)", "modules power": "Solar mod\u00fcllerinizin toplam en y\u00fcksek Watt g\u00fcc\u00fc" }, "description": "Bu de\u011ferler Solar.Forecast sonucunun ayarlanmas\u0131na izin verir. Bir alan net de\u011filse l\u00fctfen belgelere bak\u0131n." diff --git a/homeassistant/components/forecast_solar/translations/zh-Hant.json b/homeassistant/components/forecast_solar/translations/zh-Hant.json index fca97b9da01..3870ca29846 100644 --- a/homeassistant/components/forecast_solar/translations/zh-Hant.json +++ b/homeassistant/components/forecast_solar/translations/zh-Hant.json @@ -22,6 +22,7 @@ "azimuth": "\u65b9\u4f4d\u89d2\uff08360 \u5ea6\u55ae\u4f4d\u30020 = \u5317\u300190 = \u6771\u3001180 = \u5357\u3001270 = \u897f\uff09", "damping": "\u963b\u5c3c\u56e0\u7d20\uff1a\u8abf\u6574\u6e05\u6668\u8207\u508d\u665a\u7d50\u679c", "declination": "\u504f\u89d2\uff080 = \u6c34\u5e73\u300190 = \u5782\u76f4\uff09", + "inverter_size": "\u8b8a\u6d41\u5668\u5c3a\u5bf8\uff08Watt\uff09", "modules power": "\u7e3d\u5cf0\u503c\u529f\u7387" }, "description": "\u6b64\u4e9b\u6578\u503c\u5141\u8a31\u5fae\u8abf Solar.Forecast \u7d50\u679c\u3002\u5982\u679c\u6709\u4e0d\u6e05\u695a\u7684\u5730\u65b9\u3001\u8acb\u53c3\u8003\u6587\u4ef6\u8aaa\u660e\u3002" diff --git a/homeassistant/components/forked_daapd/const.py b/homeassistant/components/forked_daapd/const.py index 482218630a7..d711ae1b35b 100644 --- a/homeassistant/components/forked_daapd/const.py +++ b/homeassistant/components/forked_daapd/const.py @@ -1,21 +1,5 @@ """Const for forked-daapd.""" -from homeassistant.components.media_player.const import ( - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, -) +from homeassistant.components.media_player import MediaPlayerEntityFeature CALLBACK_TIMEOUT = 8 # max time between command and callback from forked-daapd server CONF_LIBRESPOT_JAVA_PORT = "librespot_java_port" @@ -65,23 +49,26 @@ STARTUP_DATA = { "outputs": [], } SUPPORTED_FEATURES = ( - SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_SEEK - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_SELECT_SOURCE - | SUPPORT_SHUFFLE_SET - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY_MEDIA - | SUPPORT_BROWSE_MEDIA + MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA ) SUPPORTED_FEATURES_ZONE = ( - SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF ) TTS_TIMEOUT = 20 # max time to wait between TTS getting sent and starting to play diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index eee1e136af0..d3eaf0d03d3 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -6,7 +6,7 @@ import asyncio from libpyfoscam import FoscamCamera import voluptuous as vol -from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -106,6 +106,8 @@ class HassFoscamCamera(Camera): self._unique_id = config_entry.entry_id self._rtsp_port = config_entry.data[CONF_RTSP_PORT] self._motion_status = False + if self._rtsp_port: + self._attr_supported_features = CameraEntityFeature.STREAM async def async_added_to_hass(self): """Handle entity addition to hass.""" @@ -145,14 +147,6 @@ class HassFoscamCamera(Camera): return response - @property - def supported_features(self): - """Return supported features.""" - if self._rtsp_port: - return SUPPORT_STREAM - - return None - async def stream_source(self): """Return the stream source.""" if self._rtsp_port: diff --git a/homeassistant/components/foscam/translations/ca.json b/homeassistant/components/foscam/translations/ca.json index b7f71c8c922..2fbd19dc20c 100644 --- a/homeassistant/components/foscam/translations/ca.json +++ b/homeassistant/components/foscam/translations/ca.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/cs.json b/homeassistant/components/foscam/translations/cs.json index b6f3c40abf6..ae1fc69cc77 100644 --- a/homeassistant/components/foscam/translations/cs.json +++ b/homeassistant/components/foscam/translations/cs.json @@ -18,6 +18,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json index bc1c12ea130..30d331848a4 100644 --- a/homeassistant/components/foscam/translations/de.json +++ b/homeassistant/components/foscam/translations/de.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/el.json b/homeassistant/components/foscam/translations/el.json index 0e6c9b7c65d..44aa096837c 100644 --- a/homeassistant/components/foscam/translations/el.json +++ b/homeassistant/components/foscam/translations/el.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/en.json b/homeassistant/components/foscam/translations/en.json index 16a7d0b7800..29fd01d030d 100644 --- a/homeassistant/components/foscam/translations/en.json +++ b/homeassistant/components/foscam/translations/en.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/es-419.json b/homeassistant/components/foscam/translations/es-419.json index 39027bdf914..720a14b8523 100644 --- a/homeassistant/components/foscam/translations/es-419.json +++ b/homeassistant/components/foscam/translations/es-419.json @@ -11,6 +11,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/es.json b/homeassistant/components/foscam/translations/es.json index 7e8b7c1427d..f80ef3335d1 100644 --- a/homeassistant/components/foscam/translations/es.json +++ b/homeassistant/components/foscam/translations/es.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/et.json b/homeassistant/components/foscam/translations/et.json index c21ffa0cdd1..9c2801c6135 100644 --- a/homeassistant/components/foscam/translations/et.json +++ b/homeassistant/components/foscam/translations/et.json @@ -21,6 +21,5 @@ } } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/fr.json b/homeassistant/components/foscam/translations/fr.json index f728c6d7414..dcc4b45cc83 100644 --- a/homeassistant/components/foscam/translations/fr.json +++ b/homeassistant/components/foscam/translations/fr.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/hu.json b/homeassistant/components/foscam/translations/hu.json index b303db792bb..575e2b34982 100644 --- a/homeassistant/components/foscam/translations/hu.json +++ b/homeassistant/components/foscam/translations/hu.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/id.json b/homeassistant/components/foscam/translations/id.json index 21a7682b92c..89d57eb8e4a 100644 --- a/homeassistant/components/foscam/translations/id.json +++ b/homeassistant/components/foscam/translations/id.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/it.json b/homeassistant/components/foscam/translations/it.json index 63868a0f07f..e38b7d3e8a2 100644 --- a/homeassistant/components/foscam/translations/it.json +++ b/homeassistant/components/foscam/translations/it.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ja.json b/homeassistant/components/foscam/translations/ja.json index 5a02ae5f446..3089882c63e 100644 --- a/homeassistant/components/foscam/translations/ja.json +++ b/homeassistant/components/foscam/translations/ja.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ko.json b/homeassistant/components/foscam/translations/ko.json index ba743f6b27a..762957e840e 100644 --- a/homeassistant/components/foscam/translations/ko.json +++ b/homeassistant/components/foscam/translations/ko.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/lb.json b/homeassistant/components/foscam/translations/lb.json index 123b3f4be76..11dd851f381 100644 --- a/homeassistant/components/foscam/translations/lb.json +++ b/homeassistant/components/foscam/translations/lb.json @@ -11,6 +11,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/nl.json b/homeassistant/components/foscam/translations/nl.json index 9bea23ad702..5a6bfcaa4b3 100644 --- a/homeassistant/components/foscam/translations/nl.json +++ b/homeassistant/components/foscam/translations/nl.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/no.json b/homeassistant/components/foscam/translations/no.json index 0184213de27..03b96cebe65 100644 --- a/homeassistant/components/foscam/translations/no.json +++ b/homeassistant/components/foscam/translations/no.json @@ -21,6 +21,5 @@ } } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/pl.json b/homeassistant/components/foscam/translations/pl.json index d7494e22063..03a6a47fb74 100644 --- a/homeassistant/components/foscam/translations/pl.json +++ b/homeassistant/components/foscam/translations/pl.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/pt-BR.json b/homeassistant/components/foscam/translations/pt-BR.json index b33dce1e6fe..18af16044e8 100644 --- a/homeassistant/components/foscam/translations/pt-BR.json +++ b/homeassistant/components/foscam/translations/pt-BR.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json index 8e8404c501e..1089ef63d59 100644 --- a/homeassistant/components/foscam/translations/ru.json +++ b/homeassistant/components/foscam/translations/ru.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/tr.json b/homeassistant/components/foscam/translations/tr.json index e6e5adc434c..1f8aab543fb 100644 --- a/homeassistant/components/foscam/translations/tr.json +++ b/homeassistant/components/foscam/translations/tr.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/zh-Hant.json b/homeassistant/components/foscam/translations/zh-Hant.json index d10746842a8..007a378a36b 100644 --- a/homeassistant/components/foscam/translations/zh-Hant.json +++ b/homeassistant/components/foscam/translations/zh-Hant.json @@ -21,6 +21,5 @@ } } } - }, - "title": "Foscam" + } } \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/hu.json b/homeassistant/components/freebox/translations/hu.json index 873e1057c15..39cfe189449 100644 --- a/homeassistant/components/freebox/translations/hu.json +++ b/homeassistant/components/freebox/translations/hu.json @@ -10,7 +10,7 @@ }, "step": { "link": { - "description": "Kattintson a \u201eK\u00fcld\u00e9s\u201d gombra, majd \u00e9rintse meg a jobbra mutat\u00f3 nyilat az \u00fatv\u00e1laszt\u00f3n a Freebox regisztr\u00e1l\u00e1s\u00e1hoz Home Assistant seg\u00edts\u00e9g\u00e9vel. \n\n![A gomb helye a routeren] (/static/images/config_freebox.png)", + "description": "A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben \u00e9rintse meg a jobbra mutat\u00f3 nyilat az \u00fatv\u00e1laszt\u00f3n a Freebox regisztr\u00e1l\u00e1s\u00e1hoz Home Assistant seg\u00edts\u00e9g\u00e9vel. \n\n![A gomb helye a routeren] (/static/images/config_freebox.png)", "title": "Freebox \u00fatv\u00e1laszt\u00f3 linkel\u00e9se" }, "user": { diff --git a/homeassistant/components/freebox/translations/it.json b/homeassistant/components/freebox/translations/it.json index 4eb49d5fdfb..f1978217547 100644 --- a/homeassistant/components/freebox/translations/it.json +++ b/homeassistant/components/freebox/translations/it.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "Impossibile connettersi", - "register_failed": "Errore in fase di registrazione, si prega di riprovare", + "register_failed": "Errore in fase di registrazione, riprova", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/freedompro/climate.py b/homeassistant/components/freedompro/climate.py index 8e84e4f3d51..bcd7cd109ba 100644 --- a/homeassistant/components/freedompro/climate.py +++ b/homeassistant/components/freedompro/climate.py @@ -1,4 +1,6 @@ """Support for Freedompro climate.""" +from __future__ import annotations + import json import logging @@ -7,10 +9,8 @@ from pyfreedompro import put_state from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, CONF_API_KEY, TEMP_CELSIUS @@ -25,14 +25,18 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) HVAC_MAP = { - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, + 0: HVACMode.OFF, + 1: HVACMode.HEAT, + 2: HVACMode.COOL, } HVAC_INVERT_MAP = {v: k for k, v in HVAC_MAP.items()} -SUPPORTED_HVAC_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL] +SUPPORTED_HVAC_MODES = [ + HVACMode.OFF, + HVACMode.HEAT, + HVACMode.COOL, +] async def async_setup_entry( @@ -72,10 +76,10 @@ class Device(CoordinatorEntity, ClimateEntity): model=device["type"], name=self.name, ) - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE self._attr_current_temperature = 0 self._attr_target_temperature = 0 - self._attr_hvac_mode = HVAC_MODE_OFF + self._attr_hvac_mode = HVACMode.OFF @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/freedompro/cover.py b/homeassistant/components/freedompro/cover.py index 7525d37182f..ab3914519dd 100644 --- a/homeassistant/components/freedompro/cover.py +++ b/homeassistant/components/freedompro/cover.py @@ -5,11 +5,9 @@ from pyfreedompro import put_state from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY @@ -66,7 +64,9 @@ class Device(CoordinatorEntity, CoverEntity): self._attr_current_cover_position = 0 self._attr_is_closed = True self._attr_supported_features = ( - SUPPORT_CLOSE | SUPPORT_OPEN | SUPPORT_SET_POSITION + CoverEntityFeature.CLOSE + | CoverEntityFeature.OPEN + | CoverEntityFeature.SET_POSITION ) self._attr_device_class = DEVICE_CLASS_MAP[device["type"]] diff --git a/homeassistant/components/freedompro/fan.py b/homeassistant/components/freedompro/fan.py index dc7e3bb3d9d..b7758813865 100644 --- a/homeassistant/components/freedompro/fan.py +++ b/homeassistant/components/freedompro/fan.py @@ -5,7 +5,7 @@ import json from pyfreedompro import put_state -from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant, callback @@ -51,6 +51,8 @@ class FreedomproFan(CoordinatorEntity, FanEntity): ) self._attr_is_on = False self._attr_percentage = 0 + if "rotationSpeed" in self._characteristics: + self._attr_supported_features = FanEntityFeature.SET_SPEED @property def is_on(self) -> bool | None: @@ -62,13 +64,6 @@ class FreedomproFan(CoordinatorEntity, FanEntity): """Return the current speed percentage.""" return self._attr_percentage - @property - def supported_features(self): - """Flag supported features.""" - if "rotationSpeed" in self._characteristics: - return SUPPORT_SET_SPEED - return 0 - @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" diff --git a/homeassistant/components/freedompro/light.py b/homeassistant/components/freedompro/light.py index 95731da914e..7659a136927 100644 --- a/homeassistant/components/freedompro/light.py +++ b/homeassistant/components/freedompro/light.py @@ -6,9 +6,7 @@ from pyfreedompro import put_state from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -55,11 +53,11 @@ class Device(CoordinatorEntity, LightEntity): ) self._attr_is_on = False self._attr_brightness = 0 - color_mode = COLOR_MODE_ONOFF + color_mode = ColorMode.ONOFF if "hue" in device["characteristics"]: - color_mode = COLOR_MODE_HS + color_mode = ColorMode.HS elif "brightness" in device["characteristics"]: - color_mode = COLOR_MODE_BRIGHTNESS + color_mode = ColorMode.BRIGHTNESS self._attr_color_mode = color_mode self._attr_supported_color_modes = {color_mode} diff --git a/homeassistant/components/fritz/binary_sensor.py b/homeassistant/components/fritz/binary_sensor.py index db1aac99c47..f119b7c11cb 100644 --- a/homeassistant/components/fritz/binary_sensor.py +++ b/homeassistant/components/fritz/binary_sensor.py @@ -42,6 +42,8 @@ SENSOR_TYPES: tuple[FritzBinarySensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, ), FritzBinarySensorEntityDescription( + # Deprecated, scheduled to be removed in 2022.7 (#70096) + entity_registry_enabled_default=False, key="firmware_update", name="Firmware Update", device_class=BinarySensorDeviceClass.UPDATE, diff --git a/homeassistant/components/fritz/config_flow.py b/homeassistant/components/fritz/config_flow.py index ebc5512f920..3e6961f585d 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -130,7 +130,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): ) self.context[CONF_HOST] = self._host - if ipaddress.ip_address(self._host).is_link_local: + if not self._host or ipaddress.ip_address(self._host).is_link_local: return self.async_abort(reason="ignore_ip6_link_local") if uuid := discovery_info.upnp.get(ssdp.ATTR_UPNP_UDN): @@ -148,9 +148,13 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): self.hass.config_entries.async_update_entry(entry, unique_id=uuid) return self.async_abort(reason="already_configured") - self.context["title_placeholders"] = { - "name": self._name.replace("FRITZ!Box ", "") - } + self.context.update( + { + "title_placeholders": {"name": self._name.replace("FRITZ!Box ", "")}, + "configuration_url": f"http://{self._host}", + } + ) + return await self.async_step_confirm() async def async_step_confirm( diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index a3ba907366c..77c5c02c288 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -30,6 +30,7 @@ PLATFORMS = [ Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH, + Platform.UPDATE, ] CONF_OLD_DISCOVERY = "old_discovery" diff --git a/homeassistant/components/fritz/translations/ca.json b/homeassistant/components/fritz/translations/ca.json index d39805a6302..04d5b14cac3 100644 --- a/homeassistant/components/fritz/translations/ca.json +++ b/homeassistant/components/fritz/translations/ca.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "ignore_ip6_link_local": "L'enlla\u00e7 amb adreces IPv6 locals no est\u00e0 perm\u00e8s", "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { diff --git a/homeassistant/components/fritz/translations/de.json b/homeassistant/components/fritz/translations/de.json index ae36b3450ca..d64845ba9b7 100644 --- a/homeassistant/components/fritz/translations/de.json +++ b/homeassistant/components/fritz/translations/de.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "ignore_ip6_link_local": "IPv6 link local address wird nicht unterst\u00fctzt.", "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { diff --git a/homeassistant/components/fritz/translations/el.json b/homeassistant/components/fritz/translations/el.json index d514848040f..6dfc67e08d6 100644 --- a/homeassistant/components/fritz/translations/el.json +++ b/homeassistant/components/fritz/translations/el.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "ignore_ip6_link_local": "\u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 IPv6 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9.", "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { diff --git a/homeassistant/components/fritz/translations/en.json b/homeassistant/components/fritz/translations/en.json index e7ee1568684..3ce31866c2f 100644 --- a/homeassistant/components/fritz/translations/en.json +++ b/homeassistant/components/fritz/translations/en.json @@ -10,6 +10,7 @@ "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "cannot_connect": "Failed to connect", + "connection_error": "Failed to connect", "invalid_auth": "Invalid authentication", "upnp_not_configured": "Missing UPnP settings on device." }, @@ -31,6 +32,16 @@ "description": "Update FRITZ!Box Tools credentials for: {host}.\n\nFRITZ!Box Tools is unable to log in to your FRITZ!Box.", "title": "Updating FRITZ!Box Tools - credentials" }, + "start_config": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "username": "Username" + }, + "description": "Setup FRITZ!Box Tools to control your FRITZ!Box.\nMinimum needed: username, password.", + "title": "Setup FRITZ!Box Tools - mandatory" + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritz/translations/et.json b/homeassistant/components/fritz/translations/et.json index 2051e9f4e63..89bf4a3b7c9 100644 --- a/homeassistant/components/fritz/translations/et.json +++ b/homeassistant/components/fritz/translations/et.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "H\u00e4\u00e4lestamine on k\u00e4imas", + "ignore_ip6_link_local": "IPv6 lingi kohalikku aadressi ei toetata.", "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { diff --git a/homeassistant/components/fritz/translations/fr.json b/homeassistant/components/fritz/translations/fr.json index 3219164d98b..cfcd942b530 100644 --- a/homeassistant/components/fritz/translations/fr.json +++ b/homeassistant/components/fritz/translations/fr.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "ignore_ip6_link_local": "Les adresses IPv6 de liaison locale ne sont pas prises en charge.", "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { diff --git a/homeassistant/components/fritz/translations/hu.json b/homeassistant/components/fritz/translations/hu.json index 81d3bb19e27..d1ff2c0c6bf 100644 --- a/homeassistant/components/fritz/translations/hu.json +++ b/homeassistant/components/fritz/translations/hu.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van", + "ignore_ip6_link_local": "Az IPv6-kapcsolat helyi c\u00edme nem t\u00e1mogatott.", "reauth_successful": "Az \u00fajhiteles\u00edt\u00e9s sikeres volt" }, "error": { diff --git a/homeassistant/components/fritz/translations/id.json b/homeassistant/components/fritz/translations/id.json index 816885b6391..c31bdf8b77c 100644 --- a/homeassistant/components/fritz/translations/id.json +++ b/homeassistant/components/fritz/translations/id.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Perangkat sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", + "ignore_ip6_link_local": "Alamat lokal tautan IPv6 tidak didukung.", "reauth_successful": "Autentikasi ulang berhasil" }, "error": { diff --git a/homeassistant/components/fritz/translations/it.json b/homeassistant/components/fritz/translations/it.json index bf577223b6e..0516449f5db 100644 --- a/homeassistant/components/fritz/translations/it.json +++ b/homeassistant/components/fritz/translations/it.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "ignore_ip6_link_local": "L'indirizzo locale del collegamento IPv6 non \u00e8 supportato.", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { diff --git a/homeassistant/components/fritz/translations/ja.json b/homeassistant/components/fritz/translations/ja.json index afbda92d3fe..8bd9747c9bc 100644 --- a/homeassistant/components/fritz/translations/ja.json +++ b/homeassistant/components/fritz/translations/ja.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", + "ignore_ip6_link_local": "IPv6\u30ea\u30f3\u30af\u306e\u30ed\u30fc\u30ab\u30eb\u30a2\u30c9\u30ec\u30b9\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { diff --git a/homeassistant/components/fritz/translations/nl.json b/homeassistant/components/fritz/translations/nl.json index b5577f11a0d..11fabceaf8d 100644 --- a/homeassistant/components/fritz/translations/nl.json +++ b/homeassistant/components/fritz/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", + "ignore_ip6_link_local": "Lokaal IPv6-linkadres wordt niet ondersteund.", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { diff --git a/homeassistant/components/fritz/translations/no.json b/homeassistant/components/fritz/translations/no.json index 6d6a805bab8..a18df1a7b14 100644 --- a/homeassistant/components/fritz/translations/no.json +++ b/homeassistant/components/fritz/translations/no.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "ignore_ip6_link_local": "IPv6-lenkens lokale adresse st\u00f8ttes ikke.", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { diff --git a/homeassistant/components/fritz/translations/pl.json b/homeassistant/components/fritz/translations/pl.json index 7ec1f539aab..fed010f1987 100644 --- a/homeassistant/components/fritz/translations/pl.json +++ b/homeassistant/components/fritz/translations/pl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "ignore_ip6_link_local": "Adres lokalny \u0142\u0105cza IPv6 nie jest obs\u0142ugiwany.", "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { diff --git a/homeassistant/components/fritz/translations/pt-BR.json b/homeassistant/components/fritz/translations/pt-BR.json index 2170b34b1ee..ceb295310c7 100644 --- a/homeassistant/components/fritz/translations/pt-BR.json +++ b/homeassistant/components/fritz/translations/pt-BR.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "ignore_ip6_link_local": "O endere\u00e7o local IPv6 n\u00e3o \u00e9 suportado.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { diff --git a/homeassistant/components/fritz/translations/ru.json b/homeassistant/components/fritz/translations/ru.json index 82530cca298..e45c36fe736 100644 --- a/homeassistant/components/fritz/translations/ru.json +++ b/homeassistant/components/fritz/translations/ru.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "ignore_ip6_link_local": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 IPv6 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f.", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { diff --git a/homeassistant/components/fritz/translations/tr.json b/homeassistant/components/fritz/translations/tr.json index 9a9cd3da6bf..86cabbb782b 100644 --- a/homeassistant/components/fritz/translations/tr.json +++ b/homeassistant/components/fritz/translations/tr.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "ignore_ip6_link_local": "IPv6 ba\u011flant\u0131 yerel adresi desteklenmiyor.", "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" }, "error": { diff --git a/homeassistant/components/fritz/translations/zh-Hant.json b/homeassistant/components/fritz/translations/zh-Hant.json index 7eeb3ae5e4b..778c38e36f7 100644 --- a/homeassistant/components/fritz/translations/zh-Hant.json +++ b/homeassistant/components/fritz/translations/zh-Hant.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "ignore_ip6_link_local": "\u4e0d\u652f\u63f4\u9023\u7d50\u672c\u5730\u7aef IPv6 \u4f4d\u5740", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { diff --git a/homeassistant/components/fritz/update.py b/homeassistant/components/fritz/update.py new file mode 100644 index 00000000000..620b932999e --- /dev/null +++ b/homeassistant/components/fritz/update.py @@ -0,0 +1,62 @@ +"""Support for AVM FRITZ!Box update platform.""" +from __future__ import annotations + +import logging +from typing import Any + +from homeassistant.components.update import UpdateEntity, UpdateEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .common import AvmWrapper, FritzBoxBaseEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up AVM FRITZ!Box update entities.""" + _LOGGER.debug("Setting up AVM FRITZ!Box update entities") + avm_wrapper: AvmWrapper = hass.data[DOMAIN][entry.entry_id] + + entities = [FritzBoxUpdateEntity(avm_wrapper, entry.title)] + + async_add_entities(entities) + + +class FritzBoxUpdateEntity(FritzBoxBaseEntity, UpdateEntity): + """Mixin for update entity specific attributes.""" + + _attr_supported_features = UpdateEntityFeature.INSTALL + _attr_title = "FRITZ!OS" + + def __init__( + self, + avm_wrapper: AvmWrapper, + device_friendly_name: str, + ) -> None: + """Init FRITZ!Box connectivity class.""" + self._attr_name = f"{device_friendly_name} FRITZ!OS" + self._attr_unique_id = f"{avm_wrapper.unique_id}-update" + super().__init__(avm_wrapper, device_friendly_name) + + @property + def installed_version(self) -> str | None: + """Version currently in use.""" + return self._avm_wrapper.current_firmware + + @property + def latest_version(self) -> str | None: + """Latest version available for install.""" + if self._avm_wrapper.update_available: + return self._avm_wrapper.latest_firmware + return self._avm_wrapper.current_firmware + + async def async_install( + self, version: str | None, backup: bool, **kwargs: Any + ) -> None: + """Install an update.""" + await self._avm_wrapper.async_trigger_firmware_update() diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index f27b1efa857..01e6e8ccae6 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -6,12 +6,10 @@ from typing import Any from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_COMFORT, PRESET_ECO, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -34,9 +32,7 @@ from .const import ( ) from .model import ClimateExtraAttributes -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - -OPERATION_LIST = [HVAC_MODE_HEAT, HVAC_MODE_OFF] +OPERATION_LIST = [HVACMode.HEAT, HVACMode.OFF] MIN_TEMPERATURE = 8 MAX_TEMPERATURE = 28 @@ -68,10 +64,9 @@ async def async_setup_entry( class FritzboxThermostat(FritzBoxEntity, ClimateEntity): """The thermostat class for FRITZ!SmartHome thermostats.""" - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_FLAGS + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) @property def temperature_unit(self) -> str: @@ -118,18 +113,18 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity): OFF_REPORT_SET_TEMPERATURE, OFF_API_TEMPERATURE, ): - return HVAC_MODE_OFF + return HVACMode.OFF - return HVAC_MODE_HEAT + return HVACMode.HEAT @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return OPERATION_LIST async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE) else: await self.async_set_temperature( diff --git a/homeassistant/components/fritzbox/light.py b/homeassistant/components/fritzbox/light.py index 272d170e13d..65ed96dd603 100644 --- a/homeassistant/components/fritzbox/light.py +++ b/homeassistant/components/fritzbox/light.py @@ -7,8 +7,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -25,7 +24,7 @@ from .const import ( ) from .coordinator import FritzboxDataUpdateCoordinator -SUPPORTED_COLOR_MODES = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS} +SUPPORTED_COLOR_MODES = {ColorMode.COLOR_TEMP, ColorMode.HS} async def async_setup_entry( diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 473e68ed5da..e590f14ce89 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -17,6 +17,8 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ELECTRIC_CURRENT_AMPERE, + ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, PERCENTAGE, POWER_WATT, @@ -87,6 +89,26 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = ( suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return] native_value=lambda device: device.power / 1000 if device.power else 0.0, ), + FritzSensorEntityDescription( + key="voltage", + name="Voltage", + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return] + native_value=lambda device: device.voltage / 1000 if device.voltage else 0.0, + ), + FritzSensorEntityDescription( + key="electric_current", + name="Electric Current", + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + device_class=SensorDeviceClass.CURRENT, + state_class=SensorStateClass.MEASUREMENT, + suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return] + native_value=lambda device: device.power / device.voltage + if device.power and device.voltage + else 0.0, + ), FritzSensorEntityDescription( key="total_energy", name="Total Energy", diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index efd81ddff84..c17b1fc87c9 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "ignore_ip6_link_local": "L'enlla\u00e7 amb adreces IPv6 locals no est\u00e0 perm\u00e8s", "no_devices_found": "No s'han trobat dispositius a la xarxa", "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home.", "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 7da8e616cfc..ef2a6083608 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "ignore_ip6_link_local": "IPv6 link local address wird nicht unterst\u00fctzt.", "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", "not_supported": "Verbunden mit AVM FRITZ!Box, kann jedoch keine Smart Home-Ger\u00e4te steuern.", "reauth_successful": "Die erneute Authentifizierung war erfolgreich" diff --git a/homeassistant/components/fritzbox/translations/el.json b/homeassistant/components/fritzbox/translations/el.json index 975126772d5..9e1c95e41f1 100644 --- a/homeassistant/components/fritzbox/translations/el.json +++ b/homeassistant/components/fritzbox/translations/el.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "ignore_ip6_link_local": "\u0397 \u03c4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 IPv6 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9.", "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", "not_supported": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf AVM FRITZ!Box \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Smart Home.", "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" diff --git a/homeassistant/components/fritzbox/translations/et.json b/homeassistant/components/fritzbox/translations/et.json index 849dc7fadee..3db11e7355c 100644 --- a/homeassistant/components/fritzbox/translations/et.json +++ b/homeassistant/components/fritzbox/translations/et.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", + "ignore_ip6_link_local": "IPv6 lingi kohalikku aadressi ei toetata.", "no_devices_found": "V\u00f5rgust ei leitud seadmeid", "not_supported": "\u00dchendatud AVM FRITZ!Boxiga! kuid see ei saa juhtida Smart Home seadmeid.", "reauth_successful": "Taastuvastamine \u00f5nnestus" diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json index 69f024e26d7..f9e3d354a6d 100644 --- a/homeassistant/components/fritzbox/translations/fr.json +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "ignore_ip6_link_local": "Les adresses IPv6 de liaison locale ne sont pas prises en charge.", "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", "not_supported": "Connect\u00e9 \u00e0 AVM FRITZ! Box mais impossible de contr\u00f4ler les appareils Smart Home.", "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" diff --git a/homeassistant/components/fritzbox/translations/hu.json b/homeassistant/components/fritzbox/translations/hu.json index c1cf8154aea..f079bf8e1df 100644 --- a/homeassistant/components/fritzbox/translations/hu.json +++ b/homeassistant/components/fritzbox/translations/hu.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", + "ignore_ip6_link_local": "Az IPv6-kapcsolat helyi c\u00edme nem t\u00e1mogatott.", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", "not_supported": "Csatlakoztatva az AVM FRITZ! Boxhoz, de nem tudja vez\u00e9relni az intelligens otthoni eszk\u00f6z\u00f6ket.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." diff --git a/homeassistant/components/fritzbox/translations/id.json b/homeassistant/components/fritzbox/translations/id.json index f9c4f09b4ae..63e0ebb4823 100644 --- a/homeassistant/components/fritzbox/translations/id.json +++ b/homeassistant/components/fritzbox/translations/id.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Perangkat sudah dikonfigurasi", "already_in_progress": "Alur konfigurasi sedang berlangsung", + "ignore_ip6_link_local": "Alamat lokal tautan IPv6 tidak didukung.", "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", "not_supported": "Tersambung ke AVM FRITZ!Box tetapi tidak dapat mengontrol perangkat Smart Home.", "reauth_successful": "Autentikasi ulang berhasil" diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index 68cbe08b1b9..65a819d8329 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "ignore_ip6_link_local": "L'indirizzo locale del collegamento IPv6 non \u00e8 supportato.", "no_devices_found": "Nessun dispositivo trovato sulla rete", "not_supported": "Collegato a AVM FRITZ!Box, ma non \u00e8 in grado di controllare i dispositivi Smart Home.", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" diff --git a/homeassistant/components/fritzbox/translations/ja.json b/homeassistant/components/fritzbox/translations/ja.json index c246ea5fb0d..047442d5ee7 100644 --- a/homeassistant/components/fritzbox/translations/ja.json +++ b/homeassistant/components/fritzbox/translations/ja.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", + "ignore_ip6_link_local": "IPv6\u30ea\u30f3\u30af\u306e\u30ed\u30fc\u30ab\u30eb\u30a2\u30c9\u30ec\u30b9\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002", "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", "not_supported": "AVM FRITZ!Box\u306b\u63a5\u7d9a\u3057\u307e\u3057\u305f\u304c\u3001Smart Home devices\u3092\u5236\u5fa1\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002", "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index b1be4c8214f..43d51df760d 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", + "ignore_ip6_link_local": "IPv6 link lokaal adres wordt niet ondersteund.", "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", "reauth_successful": "Herauthenticatie was succesvol" diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index 5ec0cc1acdc..98174053a61 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "ignore_ip6_link_local": "IPv6-lenkens lokale adresse st\u00f8ttes ikke.", "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter.", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json index d9832ee51a4..f1ff975af8d 100644 --- a/homeassistant/components/fritzbox/translations/pl.json +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "ignore_ip6_link_local": "Adres lokalny \u0142\u0105cza IPv6 nie jest obs\u0142ugiwany.", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", "not_supported": "Po\u0142\u0105czony z AVM FRITZ!Box, ale nie jest w stanie kontrolowa\u0107 urz\u0105dze\u0144 Smart Home", "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" diff --git a/homeassistant/components/fritzbox/translations/pt-BR.json b/homeassistant/components/fritzbox/translations/pt-BR.json index 3e3884cbc41..1fbe79772db 100644 --- a/homeassistant/components/fritzbox/translations/pt-BR.json +++ b/homeassistant/components/fritzbox/translations/pt-BR.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "ignore_ip6_link_local": "O endere\u00e7o local IPv6 n\u00e3o \u00e9 suportado.", "no_devices_found": "Nenhum dispositivo encontrado na rede", "not_supported": "Conectado ao AVM FRITZ!Box, mas n\u00e3o consegue controlar os dispositivos Smart Home.", "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 51e9aedc632..5f939462db2 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "ignore_ip6_link_local": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 IPv6 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f.", "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", "not_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e.", "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." diff --git a/homeassistant/components/fritzbox/translations/tr.json b/homeassistant/components/fritzbox/translations/tr.json index 300ca1a096a..a870cbcae11 100644 --- a/homeassistant/components/fritzbox/translations/tr.json +++ b/homeassistant/components/fritzbox/translations/tr.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "ignore_ip6_link_local": "IPv6 ba\u011flant\u0131 yerel adresi desteklenmiyor.", "no_devices_found": "A\u011fda cihaz bulunamad\u0131", "not_supported": "AVM FRITZ!Box'a ba\u011fl\u0131 ancak Ak\u0131ll\u0131 Ev cihazlar\u0131n\u0131 kontrol edemiyor.", "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" diff --git a/homeassistant/components/fritzbox/translations/zh-Hant.json b/homeassistant/components/fritzbox/translations/zh-Hant.json index b90b87aaee7..904cd81f625 100644 --- a/homeassistant/components/fritzbox/translations/zh-Hant.json +++ b/homeassistant/components/fritzbox/translations/zh-Hant.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "ignore_ip6_link_local": "\u4e0d\u652f\u63f4\u9023\u7d50\u672c\u5730\u7aef IPv6 \u4f4d\u5740", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u88dd\u7f6e\u3002", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" diff --git a/homeassistant/components/fritzbox_callmonitor/base.py b/homeassistant/components/fritzbox_callmonitor/base.py index 0db40e2098f..386e60ba199 100644 --- a/homeassistant/components/fritzbox_callmonitor/base.py +++ b/homeassistant/components/fritzbox_callmonitor/base.py @@ -1,4 +1,6 @@ """Base class for fritzbox_callmonitor entities.""" +from __future__ import annotations + from contextlib import suppress from datetime import timedelta import logging @@ -19,18 +21,26 @@ MIN_TIME_PHONEBOOK_UPDATE = timedelta(hours=6) class FritzBoxPhonebook: """This connects to a FritzBox router and downloads its phone book.""" - def __init__(self, host, username, password, phonebook_id, prefixes): + fph: FritzPhonebook + phonebook_dict: dict[str, list[str]] + number_dict: dict[str, str] + + def __init__( + self, + host: str, + username: str, + password: str, + phonebook_id: int | None = None, + prefixes: list[str] | None = None, + ) -> None: """Initialize the class.""" self.host = host self.username = username self.password = password self.phonebook_id = phonebook_id - self.phonebook_dict = None - self.number_dict = None self.prefixes = prefixes - self.fph = None - def init_phonebook(self): + def init_phonebook(self) -> None: """Establish a connection to the FRITZ!Box and check if phonebook_id is valid.""" self.fph = FritzPhonebook( address=self.host, @@ -40,7 +50,7 @@ class FritzBoxPhonebook: self.update_phonebook() @Throttle(MIN_TIME_PHONEBOOK_UPDATE) - def update_phonebook(self): + def update_phonebook(self) -> None: """Update the phone book dictionary.""" if self.phonebook_id is None: return @@ -53,17 +63,15 @@ class FritzBoxPhonebook: } _LOGGER.info("Fritz!Box phone book successfully updated") - def get_phonebook_ids(self): + def get_phonebook_ids(self) -> list[int]: """Return list of phonebook ids.""" - return self.fph.phonebook_ids + return self.fph.phonebook_ids # type: ignore[no-any-return] - def get_name(self, number): + def get_name(self, number: str) -> str: """Return a name for a given phone number.""" number = re.sub(REGEX_NUMBER, "", str(number)) - if self.number_dict is None: - return UNKNOWN_NAME - if number in self.number_dict: + with suppress(KeyError): return self.number_dict[number] if not self.prefixes: @@ -74,3 +82,5 @@ class FritzBoxPhonebook: return self.number_dict[prefix + number] with suppress(KeyError): return self.number_dict[prefix + number.lstrip("0")] + + return UNKNOWN_NAME diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index 829a3755633..5df1e7fc215 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -1,4 +1,7 @@ """Config flow for fritzbox_callmonitor.""" +from __future__ import annotations + +from typing import Any, cast from fritzconnection import FritzConnection from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError @@ -6,6 +9,7 @@ from requests.exceptions import ConnectionError as RequestsConnectionError import voluptuous as vol from homeassistant import config_entries +from homeassistant.backports.enum import StrEnum from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -14,6 +18,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult from .base import FritzBoxPhonebook from .const import ( @@ -40,11 +45,15 @@ DATA_SCHEMA_USER = vol.Schema( } ) -RESULT_INVALID_AUTH = "invalid_auth" -RESULT_INSUFFICIENT_PERMISSIONS = "insufficient_permissions" -RESULT_MALFORMED_PREFIXES = "malformed_prefixes" -RESULT_NO_DEVIES_FOUND = "no_devices_found" -RESULT_SUCCESS = "success" + +class ConnectResult(StrEnum): + """FritzBoxPhonebook connection result.""" + + INVALID_AUTH = "invalid_auth" + INSUFFICIENT_PERMISSIONS = "insufficient_permissions" + MALFORMED_PREFIXES = "malformed_prefixes" + NO_DEVIES_FOUND = "no_devices_found" + SUCCESS = "success" class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -52,21 +61,21 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): - """Initialize flow.""" - self._host = None - self._port = None - self._username = None - self._password = None - self._phonebook_name = None - self._phonebook_names = None - self._phonebook_id = None - self._phonebook_ids = None - self._fritzbox_phonebook = None - self._prefixes = None - self._serial_number = None + _host: str + _port: int + _username: str + _password: str + _phonebook_name: str + _phonebook_id: int + _phonebook_ids: list[int] + _fritzbox_phonebook: FritzBoxPhonebook + _serial_number: str - def _get_config_entry(self): + def __init__(self) -> None: + """Initialize flow.""" + self._phonebook_names: list[str] | None = None + + def _get_config_entry(self) -> FlowResult: """Create and return an config entry.""" return self.async_create_entry( title=self._phonebook_name, @@ -76,19 +85,16 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_USERNAME: self._username, CONF_PASSWORD: self._password, CONF_PHONEBOOK: self._phonebook_id, - CONF_PREFIXES: self._prefixes, SERIAL_NUMBER: self._serial_number, }, ) - def _try_connect(self): + def _try_connect(self) -> ConnectResult: """Try to connect and check auth.""" self._fritzbox_phonebook = FritzBoxPhonebook( host=self._host, username=self._username, password=self._password, - phonebook_id=self._phonebook_id, - prefixes=self._prefixes, ) try: @@ -103,24 +109,24 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) self._serial_number = device_info[FRITZ_ATTR_SERIAL_NUMBER] - return RESULT_SUCCESS + return ConnectResult.SUCCESS except RequestsConnectionError: - return RESULT_NO_DEVIES_FOUND + return ConnectResult.NO_DEVIES_FOUND except FritzSecurityError: - return RESULT_INSUFFICIENT_PERMISSIONS + return ConnectResult.INSUFFICIENT_PERMISSIONS except FritzConnectionException: - return RESULT_INVALID_AUTH + return ConnectResult.INVALID_AUTH - async def _get_name_of_phonebook(self, phonebook_id): + async def _get_name_of_phonebook(self, phonebook_id: int) -> str: """Return name of phonebook for given phonebook_id.""" phonebook_info = await self.hass.async_add_executor_job( self._fritzbox_phonebook.fph.phonebook_info, phonebook_id ) - return phonebook_info[FRITZ_ATTR_NAME] + return cast(str, phonebook_info[FRITZ_ATTR_NAME]) - async def _get_list_of_phonebook_names(self): + async def _get_list_of_phonebook_names(self) -> list[str]: """Return list of names for all available phonebooks.""" - phonebook_names = [] + phonebook_names: list[str] = [] for phonebook_id in self._phonebook_ids: phonebook_names.append(await self._get_name_of_phonebook(phonebook_id)) @@ -129,15 +135,15 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> FritzBoxCallMonitorOptionsFlowHandler: """Get the options flow for this handler.""" return FritzBoxCallMonitorOptionsFlowHandler(config_entry) - async def async_step_import(self, user_input=None): - """Handle configuration by yaml file.""" - return await self.async_step_user(user_input) - - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" if user_input is None: @@ -152,14 +158,14 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): result = await self.hass.async_add_executor_job(self._try_connect) - if result == RESULT_INVALID_AUTH: + if result == ConnectResult.INVALID_AUTH: return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA_USER, - errors={"base": RESULT_INVALID_AUTH}, + errors={"base": ConnectResult.INVALID_AUTH}, ) - if result != RESULT_SUCCESS: + if result != ConnectResult.SUCCESS: return self.async_abort(reason=result) if self.context["source"] == config_entries.SOURCE_IMPORT: @@ -178,7 +184,9 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self._get_config_entry() - async def async_step_phonebook(self, user_input=None): + async def async_step_phonebook( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow to chose one of multiple available phonebooks.""" if self._phonebook_names is None: @@ -205,23 +213,23 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class FritzBoxCallMonitorOptionsFlowHandler(config_entries.OptionsFlow): """Handle a fritzbox_callmonitor options flow.""" - def __init__(self, config_entry): + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry @classmethod - def _are_prefixes_valid(cls, prefixes): + def _are_prefixes_valid(cls, prefixes: str | None) -> bool: """Check if prefixes are valid.""" - return prefixes.strip() if prefixes else prefixes is None + return bool(prefixes.strip()) if prefixes else prefixes is None @classmethod - def _get_list_of_prefixes(cls, prefixes): + def _get_list_of_prefixes(cls, prefixes: str | None) -> list[str] | None: """Get list of prefixes.""" if prefixes is None: return None return [prefix.strip() for prefix in prefixes.split(",")] - def _get_option_schema_prefixes(self): + def _get_option_schema_prefixes(self) -> vol.Schema: """Get option schema for entering prefixes.""" return vol.Schema( { @@ -234,7 +242,9 @@ class FritzBoxCallMonitorOptionsFlowHandler(config_entries.OptionsFlow): } ) - async def async_step_init(self, user_input=None): + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the options.""" option_schema_prefixes = self._get_option_schema_prefixes() @@ -246,13 +256,13 @@ class FritzBoxCallMonitorOptionsFlowHandler(config_entries.OptionsFlow): errors={}, ) - prefixes = user_input.get(CONF_PREFIXES) + prefixes: str | None = user_input.get(CONF_PREFIXES) if not self._are_prefixes_valid(prefixes): return self.async_show_form( step_id="init", data_schema=option_schema_prefixes, - errors={"base": RESULT_MALFORMED_PREFIXES}, + errors={"base": ConnectResult.MALFORMED_PREFIXES}, ) return self.async_create_entry( diff --git a/homeassistant/components/fritzbox_callmonitor/const.py b/homeassistant/components/fritzbox_callmonitor/const.py index 435bfdef87e..9939a73bc18 100644 --- a/homeassistant/components/fritzbox_callmonitor/const.py +++ b/homeassistant/components/fritzbox_callmonitor/const.py @@ -1,17 +1,20 @@ """Constants for the AVM Fritz!Box call monitor integration.""" +from typing import Final + +from homeassistant.backports.enum import StrEnum from homeassistant.const import Platform -STATE_RINGING = "ringing" -STATE_DIALING = "dialing" -STATE_TALKING = "talking" -STATE_IDLE = "idle" -FRITZ_STATE_RING = "RING" -FRITZ_STATE_CALL = "CALL" -FRITZ_STATE_CONNECT = "CONNECT" -FRITZ_STATE_DISCONNECT = "DISCONNECT" +class FritzState(StrEnum): + """Fritz!Box call states.""" -ICON_PHONE = "mdi:phone" + RING = "RING" + CALL = "CALL" + CONNECT = "CONNECT" + DISCONNECT = "DISCONNECT" + + +ICON_PHONE: Final = "mdi:phone" ATTR_PREFIXES = "prefixes" @@ -34,9 +37,9 @@ DEFAULT_USERNAME = "admin" DEFAULT_PHONEBOOK = 0 DEFAULT_NAME = "Phone" -DOMAIN = "fritzbox_callmonitor" -MANUFACTURER = "AVM" +DOMAIN: Final = "fritzbox_callmonitor" +MANUFACTURER: Final = "AVM" PLATFORMS = [Platform.SENSOR] -UNDO_UPDATE_LISTENER = "undo_update_listener" -FRITZBOX_PHONEBOOK = "fritzbox_phonebook" +UNDO_UPDATE_LISTENER: Final = "undo_update_listener" +FRITZBOX_PHONEBOOK: Final = "fritzbox_phonebook" diff --git a/homeassistant/components/fritzbox_callmonitor/manifest.json b/homeassistant/components/fritzbox_callmonitor/manifest.json index a33e01153b7..3c89f68dc11 100644 --- a/homeassistant/components/fritzbox_callmonitor/manifest.json +++ b/homeassistant/components/fritzbox_callmonitor/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor", "requirements": ["fritzconnection==1.8.0"], - "codeowners": [], + "codeowners": ["@cdce8p"], "iot_class": "local_polling", "loggers": ["fritzconnection"] } diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 5efb0776c41..ed2be40f30f 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -1,92 +1,49 @@ """Sensor to monitor incoming/outgoing phone calls on a Fritz!Box router.""" from __future__ import annotations +from collections.abc import Mapping from datetime import datetime, timedelta import logging import queue from threading import Event as ThreadingEvent, Thread from time import sleep +from typing import Any, cast from fritzconnection.core.fritzmonitor import FritzMonitor -import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP, -) -from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv +from homeassistant.backports.enum import StrEnum +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Event, HomeAssistant from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from .base import FritzBoxPhonebook from .const import ( ATTR_PREFIXES, CONF_PHONEBOOK, CONF_PREFIXES, - DEFAULT_HOST, - DEFAULT_NAME, - DEFAULT_PHONEBOOK, - DEFAULT_PORT, - DEFAULT_USERNAME, DOMAIN, - FRITZ_STATE_CALL, - FRITZ_STATE_CONNECT, - FRITZ_STATE_DISCONNECT, - FRITZ_STATE_RING, FRITZBOX_PHONEBOOK, ICON_PHONE, MANUFACTURER, SERIAL_NUMBER, - STATE_DIALING, - STATE_IDLE, - STATE_RINGING, - STATE_TALKING, - UNKNOWN_NAME, + FritzState, ) _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(hours=3) -# Deprecated in Home Assistant 2022.3 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PHONEBOOK, default=DEFAULT_PHONEBOOK): cv.positive_int, - vol.Optional(CONF_PREFIXES): vol.All(cv.ensure_list, [cv.string]), - } -) +class CallState(StrEnum): + """Fritz sensor call states.""" -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Import the platform into a config entry.""" - _LOGGER.warning( - "Configuration of the AVM FRITZ!Box Call Monitor sensor platform in YAML " - "is deprecated and will be removed in Home Assistant 2022.5; " - "Your existing configuration has been imported into the UI automatically " - "and can be safely removed from your configuration.yaml file" - ) - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=config - ) - ) + RINGING = "ringing" + DIALING = "dialing" + TALKING = "talking" + IDLE = "idle" async def async_setup_entry( @@ -95,14 +52,16 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the fritzbox_callmonitor sensor from config_entry.""" - fritzbox_phonebook = hass.data[DOMAIN][config_entry.entry_id][FRITZBOX_PHONEBOOK] + fritzbox_phonebook: FritzBoxPhonebook = hass.data[DOMAIN][config_entry.entry_id][ + FRITZBOX_PHONEBOOK + ] - phonebook_name = config_entry.title - phonebook_id = config_entry.data[CONF_PHONEBOOK] - prefixes = config_entry.options.get(CONF_PREFIXES) - serial_number = config_entry.data[SERIAL_NUMBER] - host = config_entry.data[CONF_HOST] - port = config_entry.data[CONF_PORT] + phonebook_name: str = config_entry.title + phonebook_id: int = config_entry.data[CONF_PHONEBOOK] + prefixes: list[str] | None = config_entry.options.get(CONF_PREFIXES) + serial_number: str = config_entry.data[SERIAL_NUMBER] + host: str = config_entry.data[CONF_HOST] + port: int = config_entry.data[CONF_PORT] name = f"{fritzbox_phonebook.fph.modelname} Call Monitor {phonebook_name}" unique_id = f"{serial_number}-{phonebook_id}" @@ -116,30 +75,59 @@ async def async_setup_entry( port=port, ) - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, sensor.async_will_remove_from_hass() - ) - async_add_entities([sensor]) class FritzBoxCallSensor(SensorEntity): """Implementation of a Fritz!Box call monitor.""" - def __init__(self, name, unique_id, fritzbox_phonebook, prefixes, host, port): + _attr_icon = ICON_PHONE + + def __init__( + self, + name: str, + unique_id: str, + fritzbox_phonebook: FritzBoxPhonebook, + prefixes: list[str] | None, + host: str, + port: int, + ) -> None: """Initialize the sensor.""" - self._state = STATE_IDLE - self._attributes = {} - self._name = name.title() - self._unique_id = unique_id self._fritzbox_phonebook = fritzbox_phonebook self._prefixes = prefixes self._host = host self._port = port - self._monitor = None + self._monitor: FritzBoxCallMonitor | None = None + self._attributes: dict[str, str | list[str]] = {} - async def async_added_to_hass(self): + self._attr_name = name.title() + self._attr_unique_id = unique_id + self._attr_native_value = CallState.IDLE + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, unique_id)}, + manufacturer=MANUFACTURER, + model=self._fritzbox_phonebook.fph.modelname, + name=self._fritzbox_phonebook.fph.modelname, + sw_version=self._fritzbox_phonebook.fph.fc.system_version, + ) + + async def async_added_to_hass(self) -> None: """Connect to FRITZ!Box to monitor its call state.""" + await super().async_added_to_hass() + await self.hass.async_add_executor_job(self._start_call_monitor) + self.async_on_remove( + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self._stop_call_monitor + ) + ) + + async def async_will_remove_from_hass(self) -> None: + """Disconnect from FRITZ!Box by stopping monitor.""" + await super().async_will_remove_from_hass() + await self.hass.async_add_executor_job(self._stop_call_monitor) + + def _start_call_monitor(self) -> None: + """Check connection and start callmonitor thread.""" _LOGGER.debug("Starting monitor for: %s", self.entity_id) self._monitor = FritzBoxCallMonitor( host=self._host, @@ -148,8 +136,8 @@ class FritzBoxCallSensor(SensorEntity): ) self._monitor.connect() - async def async_will_remove_from_hass(self): - """Disconnect from FRITZ!Box by stopping monitor.""" + def _stop_call_monitor(self, event: Event | None = None) -> None: + """Stop callmonitor thread.""" if ( self._monitor and self._monitor.stopped @@ -161,86 +149,47 @@ class FritzBoxCallSensor(SensorEntity): self._monitor.connection.stop() _LOGGER.debug("Stopped monitor for: %s", self.entity_id) - def set_state(self, state): + def set_state(self, state: CallState) -> None: """Set the state.""" - self._state = state + self._attr_native_value = state - def set_attributes(self, attributes): + def set_attributes(self, attributes: Mapping[str, str]) -> None: """Set the state attributes.""" - self._attributes = attributes + self._attributes = {**attributes} @property - def name(self): - """Return name of this sensor.""" - return self._name - - @property - def should_poll(self): - """Only poll to update phonebook, if defined.""" - return self._fritzbox_phonebook is not None - - @property - def native_value(self): - """Return the state of the device.""" - return self._state - - @property - def icon(self): - """Return the icon of the sensor.""" - return ICON_PHONE - - @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, str | list[str]]: """Return the state attributes.""" if self._prefixes: self._attributes[ATTR_PREFIXES] = self._prefixes return self._attributes - @property - def device_info(self) -> DeviceInfo: - """Return device specific attributes.""" - return DeviceInfo( - identifiers={(DOMAIN, self._unique_id)}, - manufacturer=MANUFACTURER, - model=self._fritzbox_phonebook.fph.modelname, - name=self._fritzbox_phonebook.fph.modelname, - sw_version=self._fritzbox_phonebook.fph.fc.system_version, - ) - - @property - def unique_id(self): - """Return the unique ID of the device.""" - return self._unique_id - - def number_to_name(self, number): + def number_to_name(self, number: str) -> str: """Return a name for a given phone number.""" - if self._fritzbox_phonebook is None: - return UNKNOWN_NAME return self._fritzbox_phonebook.get_name(number) - def update(self): + def update(self) -> None: """Update the phonebook if it is defined.""" - if self._fritzbox_phonebook is not None: - self._fritzbox_phonebook.update_phonebook() + self._fritzbox_phonebook.update_phonebook() class FritzBoxCallMonitor: """Event listener to monitor calls on the Fritz!Box.""" - def __init__(self, host, port, sensor): + def __init__(self, host: str, port: int, sensor: FritzBoxCallSensor) -> None: """Initialize Fritz!Box monitor instance.""" self.host = host self.port = port - self.connection = None + self.connection: FritzMonitor | None = None self.stopped = ThreadingEvent() self._sensor = sensor - def connect(self): + def connect(self) -> None: """Connect to the Fritz!Box.""" _LOGGER.debug("Setting up socket connection") try: self.connection = FritzMonitor(address=self.host, port=self.port) - kwargs = {"event_queue": self.connection.start()} + kwargs: dict[str, Any] = {"event_queue": self.connection.start()} Thread(target=self._process_events, kwargs=kwargs).start() except OSError as err: self.connection = None @@ -248,14 +197,17 @@ class FritzBoxCallMonitor: "Cannot connect to %s on port %s: %s", self.host, self.port, err ) - def _process_events(self, event_queue): + def _process_events(self, event_queue: queue.Queue[str]) -> None: """Listen to incoming or outgoing calls.""" _LOGGER.debug("Connection established, waiting for events") while not self.stopped.is_set(): try: event = event_queue.get(timeout=10) except queue.Empty: - if not self.connection.is_alive and not self.stopped.is_set(): + if ( + not cast(FritzMonitor, self.connection).is_alive + and not self.stopped.is_set() + ): _LOGGER.error("Connection has abruptly ended") _LOGGER.debug("Empty event queue") continue @@ -264,14 +216,14 @@ class FritzBoxCallMonitor: self._parse(event) sleep(1) - def _parse(self, line): + def _parse(self, event: str) -> None: """Parse the call information and set the sensor states.""" - line = line.split(";") + line = event.split(";") df_in = "%d.%m.%y %H:%M:%S" df_out = "%Y-%m-%dT%H:%M:%S" isotime = datetime.strptime(line[0], df_in).strftime(df_out) - if line[1] == FRITZ_STATE_RING: - self._sensor.set_state(STATE_RINGING) + if line[1] == FritzState.RING: + self._sensor.set_state(CallState.RINGING) att = { "type": "incoming", "from": line[3], @@ -281,8 +233,8 @@ class FritzBoxCallMonitor: "from_name": self._sensor.number_to_name(line[3]), } self._sensor.set_attributes(att) - elif line[1] == FRITZ_STATE_CALL: - self._sensor.set_state(STATE_DIALING) + elif line[1] == FritzState.CALL: + self._sensor.set_state(CallState.DIALING) att = { "type": "outgoing", "from": line[4], @@ -292,8 +244,8 @@ class FritzBoxCallMonitor: "to_name": self._sensor.number_to_name(line[5]), } self._sensor.set_attributes(att) - elif line[1] == FRITZ_STATE_CONNECT: - self._sensor.set_state(STATE_TALKING) + elif line[1] == FritzState.CONNECT: + self._sensor.set_state(CallState.TALKING) att = { "with": line[4], "device": line[3], @@ -301,8 +253,8 @@ class FritzBoxCallMonitor: "with_name": self._sensor.number_to_name(line[4]), } self._sensor.set_attributes(att) - elif line[1] == FRITZ_STATE_DISCONNECT: - self._sensor.set_state(STATE_IDLE) + elif line[1] == FritzState.DISCONNECT: + self._sensor.set_state(CallState.IDLE) att = {"duration": line[3], "closed": isotime} self._sensor.set_attributes(att) self._sensor.schedule_update_ha_state() diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 803b093fd40..0c540a477ef 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -360,20 +360,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if os.path.isdir(local): hass.http.register_static_path("/local", local, not is_dev) + # Can be removed in 2023 + hass.http.register_redirect("/config/server_control", "/developer-tools/yaml") + hass.http.app.router.register_resource(IndexView(repo_path, hass)) async_register_built_in_panel(hass, "profile") - # To smooth transition to new urls, add redirects to new urls of dev tools - # Added June 27, 2019. Can be removed in 2021. - for panel in ("event", "service", "state", "template"): - hass.http.register_redirect(f"/dev-{panel}", f"/developer-tools/{panel}") - for panel in ("logs", "info", "mqtt"): - # Can be removed in 2021. - hass.http.register_redirect(f"/dev-{panel}", f"/config/{panel}") - # Added June 20 2020. Can be removed in 2022. - hass.http.register_redirect(f"/developer-tools/{panel}", f"/config/{panel}") - async_register_built_in_panel( hass, "developer-tools", @@ -533,6 +526,7 @@ class IndexView(web_urldispatcher.AbstractResource): """ if ( request.path != "/" + and len(request.url.parts) > 1 and request.url.parts[1] not in self.hass.data[DATA_PANELS] ): return None, set() @@ -610,7 +604,7 @@ class ManifestJSONView(HomeAssistantView): name = "manifestjson" @callback - def get(self, request: web.Request) -> web.Response: # pylint: disable=no-self-use + def get(self, request: web.Request) -> web.Response: """Return the manifest.json.""" return web.Response( text=MANIFEST_JSON.json, content_type="application/manifest+json" diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index a26018e05ce..b51219c4f19 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20220405.0"], + "requirements": ["home-assistant-frontend==20220504.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index a7829e23c57..034762a09ac 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -7,23 +7,12 @@ from afsapi import AFSAPI import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -42,22 +31,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) -SUPPORT_FRONTIER_SILICON = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_STEP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SEEK - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_STOP - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE -) - DEFAULT_PORT = 80 DEFAULT_PASSWORD = "1234" @@ -104,6 +77,22 @@ async def async_setup_platform( class AFSAPIDevice(MediaPlayerEntity): """Representation of a Frontier Silicon device on the network.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, device_url, password, name): """Initialize the Frontier Silicon API device.""" self._device_url = device_url @@ -158,11 +147,6 @@ class AFSAPIDevice(MediaPlayerEntity): """Content type of current playing media.""" return MEDIA_TYPE_MUSIC - @property - def supported_features(self): - """Flag of media commands that are supported.""" - return SUPPORT_FRONTIER_SILICON - @property def state(self): """Return the state of the player.""" diff --git a/homeassistant/components/futurenow/light.py b/homeassistant/components/futurenow/light.py index 4cd61865706..e27436966fc 100644 --- a/homeassistant/components/futurenow/light.py +++ b/homeassistant/components/futurenow/light.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_NAME, CONF_PORT @@ -106,11 +106,16 @@ class FutureNowLight(LightEntity): return self._brightness @property - def supported_features(self): - """Flag supported features.""" + def color_mode(self) -> str: + """Return the color mode of the light.""" if self._dimmable: - return SUPPORT_BRIGHTNESS - return 0 + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[str] | None: + """Flag supported color modes.""" + return {self.color_mode} def turn_on(self, **kwargs): """Turn the light on.""" diff --git a/homeassistant/components/generic/__init__.py b/homeassistant/components/generic/__init__.py index f243f1639b3..cb669d8b906 100644 --- a/homeassistant/components/generic/__init__.py +++ b/homeassistant/components/generic/__init__.py @@ -1,8 +1,12 @@ """The generic component.""" +from __future__ import annotations + +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er DOMAIN = "generic" PLATFORMS = [Platform.CAMERA] @@ -13,9 +17,25 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> Non await hass.config_entries.async_reload(entry.entry_id) +async def _async_migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Migrate entities to the new unique id.""" + + @callback + def _async_migrator(entity_entry: er.RegistryEntry) -> dict[str, Any] | None: + if entity_entry.unique_id == entry.entry_id: + # Already correct, nothing to do + return None + # There is only one entity, and its unique id + # should always be the same as the config entry entry_id + return {"new_unique_id": entry.entry_id} + + await er.async_migrate_entries(hass, entry.entry_id, _async_migrator) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up generic IP camera from a config entry.""" + await _async_migrate_unique_ids(hass, entry) hass.config_entries.async_setup_platforms(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(_async_update_listener)) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 72fec27b733..d20032e2607 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -9,8 +9,8 @@ import voluptuous as vol from homeassistant.components.camera import ( DEFAULT_CONTENT_TYPE, PLATFORM_SCHEMA, - SUPPORT_STREAM, Camera, + CameraEntityFeature, ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( @@ -109,7 +109,7 @@ async def async_setup_entry( """Set up a generic IP Camera.""" async_add_entities( - [GenericCamera(hass, entry.options, entry.unique_id, entry.title)] + [GenericCamera(hass, entry.options, entry.entry_id, entry.title)] ) @@ -150,7 +150,9 @@ class GenericCamera(Camera): self._stream_source.hass = hass self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE] self._attr_frame_interval = 1 / device_info[CONF_FRAMERATE] - self._supported_features = SUPPORT_STREAM if self._stream_source else 0 + self._attr_supported_features = ( + CameraEntityFeature.STREAM if self._stream_source else 0 + ) self.content_type = device_info[CONF_CONTENT_TYPE] self.verify_ssl = device_info[CONF_VERIFY_SSL] if device_info.get(CONF_RTSP_TRANSPORT): @@ -162,11 +164,6 @@ class GenericCamera(Camera): self._last_url = None self._last_image = None - @property - def supported_features(self): - """Return supported features for this camera.""" - return self._supported_features - async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 55541a6ea68..086262aa0a1 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -94,7 +94,7 @@ def build_schema( vol.Required( CONF_FRAMERATE, description={"suggested_value": user_input.get(CONF_FRAMERATE, 2)}, - ): int, + ): vol.All(vol.Range(min=0, min_included=False), cv.positive_float), vol.Required( CONF_VERIFY_SSL, default=user_input.get(CONF_VERIFY_SSL, True) ): bool, @@ -130,10 +130,9 @@ async def async_test_still(hass, info) -> tuple[dict[str, str], str | None]: fmt = None if not (url := info.get(CONF_STILL_IMAGE_URL)): return {}, info.get(CONF_CONTENT_TYPE, "image/jpeg") - if not isinstance(url, template_helper.Template) and url: - url = cv.template(url) - url.hass = hass try: + if not isinstance(url, template_helper.Template): + url = template_helper.Template(url, hass) url = url.async_render(parse_result=False) except TemplateError as err: _LOGGER.warning("Problem rendering template %s: %s", url, err) @@ -168,11 +167,20 @@ async def async_test_still(hass, info) -> tuple[dict[str, str], str | None]: return {}, f"image/{fmt}" -def slug_url(url) -> str | None: +def slug(hass, template) -> str | None: """Convert a camera url into a string suitable for a camera name.""" - if not url: + if not template: return None - return slugify(yarl.URL(url).host) + if not isinstance(template, template_helper.Template): + template = template_helper.Template(template, hass) + try: + url = template.async_render(parse_result=False) + return slugify(yarl.URL(url).host) + except TemplateError as err: + _LOGGER.error("Syntax error in '%s': %s", template.template, err) + except (ValueError, TypeError) as err: + _LOGGER.error("Syntax error in '%s': %s", url, err) + return None async def async_test_stream(hass, info) -> dict[str, str]: @@ -252,6 +260,7 @@ class GenericIPCamConfigFlow(ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Handle the start of the config flow.""" errors = {} + hass = self.hass if user_input: # Secondary validation because serialised vol can't seem to handle this complexity: if not user_input.get(CONF_STILL_IMAGE_URL) and not user_input.get( @@ -263,8 +272,7 @@ class GenericIPCamConfigFlow(ConfigFlow, domain=DOMAIN): errors = errors | await async_test_stream(self.hass, user_input) still_url = user_input.get(CONF_STILL_IMAGE_URL) stream_url = user_input.get(CONF_STREAM_SOURCE) - name = slug_url(still_url) or slug_url(stream_url) or DEFAULT_NAME - + name = slug(hass, still_url) or slug(hass, stream_url) or DEFAULT_NAME if not errors: user_input[CONF_CONTENT_TYPE] = still_format user_input[CONF_LIMIT_REFETCH_TO_URL_CHANGE] = False @@ -274,7 +282,6 @@ class GenericIPCamConfigFlow(ConfigFlow, domain=DOMAIN): # is always jpeg user_input[CONF_CONTENT_TYPE] = "image/jpeg" - await self.async_set_unique_id(self.flow_id) return self.async_create_entry( title=name, data={}, options=user_input ) @@ -296,13 +303,13 @@ class GenericIPCamConfigFlow(ConfigFlow, domain=DOMAIN): still_url = import_config.get(CONF_STILL_IMAGE_URL) stream_url = import_config.get(CONF_STREAM_SOURCE) name = import_config.get( - CONF_NAME, slug_url(still_url) or slug_url(stream_url) or DEFAULT_NAME + CONF_NAME, + slug(self.hass, still_url) or slug(self.hass, stream_url) or DEFAULT_NAME, ) if CONF_LIMIT_REFETCH_TO_URL_CHANGE not in import_config: import_config[CONF_LIMIT_REFETCH_TO_URL_CHANGE] = False still_format = import_config.get(CONF_CONTENT_TYPE, "image/jpeg") import_config[CONF_CONTENT_TYPE] = still_format - await self.async_set_unique_id(self.flow_id) return self.async_create_entry(title=name, data={}, options=import_config) @@ -320,6 +327,7 @@ class GenericOptionsFlowHandler(OptionsFlow): ) -> FlowResult: """Manage Generic IP Camera options.""" errors: dict[str, str] = {} + hass = self.hass if user_input is not None: errors, still_format = await async_test_still( @@ -329,7 +337,7 @@ class GenericOptionsFlowHandler(OptionsFlow): still_url = user_input.get(CONF_STILL_IMAGE_URL) stream_url = user_input.get(CONF_STREAM_SOURCE) if not errors: - title = slug_url(still_url) or slug_url(stream_url) or DEFAULT_NAME + title = slug(hass, still_url) or slug(hass, stream_url) or DEFAULT_NAME if still_url is None: # If user didn't specify a still image URL, # The automatically generated still image that stream generates diff --git a/homeassistant/components/generic/manifest.json b/homeassistant/components/generic/manifest.json index 0bb94f2b832..ae14c3c4d03 100644 --- a/homeassistant/components/generic/manifest.json +++ b/homeassistant/components/generic/manifest.json @@ -2,7 +2,7 @@ "domain": "generic", "name": "Generic Camera", "config_flow": true, - "requirements": ["ha-av==9.1.1-3", "pillow==9.0.1"], + "requirements": ["av==9.2.0", "pillow==9.1.0"], "documentation": "https://www.home-assistant.io/integrations/generic", "codeowners": ["@davet2001"], "iot_class": "local_push" diff --git a/homeassistant/components/generic/translations/bg.json b/homeassistant/components/generic/translations/bg.json new file mode 100644 index 00000000000..dc9bd439f0b --- /dev/null +++ b/homeassistant/components/generic/translations/bg.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430", + "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435\u0442\u043e?" + }, + "content_type": { + "data": { + "content_type": "\u0422\u0438\u043f \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435" + }, + "description": "\u041f\u043e\u0441\u043e\u0447\u0435\u0442\u0435 \u0442\u0438\u043f\u0430 \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435 \u0437\u0430 \u043f\u043e\u0442\u043e\u043a\u0430." + }, + "user": { + "data": { + "authentication": "\u0423\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435", + "content_type": "\u0422\u0438\u043f \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "rtsp_transport": "RTSP \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0435\u043d \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + }, + "options": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "content_type": { + "data": { + "content_type": "\u0422\u0438\u043f \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435" + }, + "description": "\u041f\u043e\u0441\u043e\u0447\u0435\u0442\u0435 \u0442\u0438\u043f\u0430 \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435 \u0437\u0430 \u043f\u043e\u0442\u043e\u043a\u0430." + }, + "init": { + "data": { + "authentication": "\u0423\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435", + "content_type": "\u0422\u0438\u043f \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "rtsp_transport": "RTSP \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0435\u043d \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/ca.json b/homeassistant/components/generic/translations/ca.json new file mode 100644 index 00000000000..3c2f5055ba5 --- /dev/null +++ b/homeassistant/components/generic/translations/ca.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "already_exists": "Ja hi ha una c\u00e0mera amb aquest URL de configuraci\u00f3.", + "invalid_still_image": "L'URL no ha retornat una imatge fixa v\u00e0lida", + "no_still_image_or_stream_url": "Has d'especificar almenys una imatge un URL de flux", + "stream_file_not_found": "Fitxer no trobat mentre s'intentava connectar al flux de dades (est\u00e0 instal\u00b7lat ffmpeg?)", + "stream_http_not_found": "HTTP 404 'Not found' a l'intentar connectar-se al flux de dades ('stream')", + "stream_io_error": "Error d'entrada/sortida mentre s'intentava connectar al flux de dades. Protocol de transport RTSP incorrecte?", + "stream_no_route_to_host": "No s'ha pogut trobar l'amfitri\u00f3 mentre intentava connectar al flux de dades", + "stream_no_video": "El flux no cont\u00e9 v\u00eddeo", + "stream_not_permitted": "Operaci\u00f3 no permesa mentre s'intentava connectar al flux de dades. Protocol de transport RTSP incorrecte?", + "stream_unauthorised": "L'autoritzaci\u00f3 ha fallat mentre s'intentava connectar amb el flux de dades", + "timeout": "El temps m\u00e0xim de c\u00e0rrega de l'URL ha expirat", + "unable_still_load": "No s'ha pogut carregar cap imatge v\u00e0lida des de l'URL d'imatge fixa (pot ser per un amfitri\u00f3 o URL inv\u00e0lid o un error d'autenticaci\u00f3). Revisa els registres per a m\u00e9s informaci\u00f3.", + "unknown": "Error inesperat" + }, + "step": { + "confirm": { + "description": "Vols comen\u00e7ar la configuraci\u00f3?" + }, + "content_type": { + "data": { + "content_type": "Tipus de contingut" + }, + "description": "Especifica el tipus de contingut per al flux de dades (stream)." + }, + "user": { + "data": { + "authentication": "Autenticaci\u00f3", + "content_type": "Tipus de contingut", + "framerate": "Freq\u00fc\u00e8ncia de visualitzaci\u00f3 (Hz)", + "limit_refetch_to_url_change": "Limita la lectura al canvi d'URL", + "password": "Contrasenya", + "rtsp_transport": "Protocol de transport RTSP", + "still_image_url": "URL d'imatge fixa (p. ex. http://...)", + "stream_source": "URL origen del flux (p. ex. rtsp://...)", + "username": "Nom d'usuari", + "verify_ssl": "Verifica el certificat SSL" + }, + "description": "Introdueix la configuraci\u00f3 de connexi\u00f3 amb la c\u00e0mera." + } + } + }, + "options": { + "error": { + "already_exists": "Ja hi ha una c\u00e0mera amb aquest URL de configuraci\u00f3.", + "invalid_still_image": "L'URL no ha retornat una imatge fixa v\u00e0lida", + "no_still_image_or_stream_url": "Has d'especificar almenys una imatge un URL de flux", + "stream_file_not_found": "Fitxer no trobat mentre s'intentava connectar al flux de dades (est\u00e0 instal\u00b7lat ffmpeg?)", + "stream_http_not_found": "HTTP 404 'Not found' a l'intentar connectar-se al flux de dades ('stream')", + "stream_io_error": "Error d'entrada/sortida mentre s'intentava connectar al flux de dades. Protocol de transport RTSP incorrecte?", + "stream_no_route_to_host": "No s'ha pogut trobar l'amfitri\u00f3 mentre intentava connectar al flux de dades", + "stream_no_video": "El flux no cont\u00e9 v\u00eddeo", + "stream_not_permitted": "Operaci\u00f3 no permesa mentre s'intentava connectar al flux de dades. Protocol de transport RTSP incorrecte?", + "stream_unauthorised": "L'autoritzaci\u00f3 ha fallat mentre s'intentava connectar amb el flux de dades", + "timeout": "El temps m\u00e0xim de c\u00e0rrega de l'URL ha expirat", + "unable_still_load": "No s'ha pogut carregar cap imatge v\u00e0lida des de l'URL d'imatge fixa (pot ser per un amfitri\u00f3 o URL inv\u00e0lid o un error d'autenticaci\u00f3). Revisa els registres per a m\u00e9s informaci\u00f3.", + "unknown": "Error inesperat" + }, + "step": { + "content_type": { + "data": { + "content_type": "Tipus de contingut" + }, + "description": "Especifica el tipus de contingut per al flux de dades (stream)." + }, + "init": { + "data": { + "authentication": "Autenticaci\u00f3", + "content_type": "Tipus de contingut", + "framerate": "Freq\u00fc\u00e8ncia de visualitzaci\u00f3 (Hz)", + "limit_refetch_to_url_change": "Limita la lectura al canvi d'URL", + "password": "Contrasenya", + "rtsp_transport": "Protocol de transport RTSP", + "still_image_url": "URL d'imatge fixa (p. ex. http://...)", + "stream_source": "URL origen del flux (p. ex. rtsp://...)", + "username": "Nom d'usuari", + "verify_ssl": "Verifica el certificat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/cs.json b/homeassistant/components/generic/translations/cs.json new file mode 100644 index 00000000000..520e0743edd --- /dev/null +++ b/homeassistant/components/generic/translations/cs.json @@ -0,0 +1,33 @@ +{ + "config": { + "step": { + "content_type": { + "data": { + "content_type": "Typ obsahu" + } + }, + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + }, + "options": { + "step": { + "content_type": { + "data": { + "content_type": "Typ obsahu" + } + }, + "init": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no", + "verify_ssl": "Ov\u011b\u0159it certifik\u00e1t SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/de.json b/homeassistant/components/generic/translations/de.json new file mode 100644 index 00000000000..849555dcc5e --- /dev/null +++ b/homeassistant/components/generic/translations/de.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "already_exists": "Es existiert bereits eine Kamera mit diesen URL-Einstellungen.", + "invalid_still_image": "URL hat kein g\u00fcltiges Standbild zur\u00fcckgegeben", + "no_still_image_or_stream_url": "Du musst mindestens eine Standbild- oder Stream-URL angeben", + "stream_file_not_found": "Datei nicht gefunden beim Versuch, eine Verbindung zum Stream herzustellen (ist ffmpeg installiert?)", + "stream_http_not_found": "HTTP 404 Not found beim Versuch, eine Verbindung zum Stream herzustellen", + "stream_io_error": "Eingabe-/Ausgabefehler beim Versuch, eine Verbindung zum Stream herzustellen. Falsches RTSP-Transportprotokoll?", + "stream_no_route_to_host": "Beim Versuch, eine Verbindung zum Stream herzustellen, konnte der Host nicht gefunden werden", + "stream_no_video": "Stream enth\u00e4lt kein Video", + "stream_not_permitted": "Beim Versuch, eine Verbindung zum Stream herzustellen, ist ein Vorgang nicht zul\u00e4ssig. Falsches RTSP-Transportprotokoll?", + "stream_unauthorised": "Autorisierung beim Versuch, eine Verbindung zum Stream herzustellen, fehlgeschlagen", + "timeout": "Zeit\u00fcberschreitung beim Laden der URL", + "unable_still_load": "Es konnte kein g\u00fcltiges Bild von der Standbild-URL geladen werden (z. B. ung\u00fcltiger Host, URL oder Authentifizierungsfehler). \u00dcberpr\u00fcfe das Protokoll f\u00fcr weitere Informationen.", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "confirm": { + "description": "M\u00f6chtest Du mit der Einrichtung beginnen?" + }, + "content_type": { + "data": { + "content_type": "Inhaltstyp" + }, + "description": "Gib den Inhaltstyp des Streams an." + }, + "user": { + "data": { + "authentication": "Authentifizierung", + "content_type": "Inhaltstyp", + "framerate": "Bildfrequenz (Hz)", + "limit_refetch_to_url_change": "Neuabruf auf URL-\u00c4nderung beschr\u00e4nken", + "password": "Passwort", + "rtsp_transport": "RTSP-Transportprotokoll", + "still_image_url": "Standbild-URL (z.B. http://...)", + "stream_source": "Stream-Quell-URL (z.B. rtsp://...)", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + }, + "description": "Gib die Einstellungen f\u00fcr die Verbindung mit der Kamera ein." + } + } + }, + "options": { + "error": { + "already_exists": "Es existiert bereits eine Kamera mit diesen URL-Einstellungen.", + "invalid_still_image": "URL hat kein g\u00fcltiges Standbild zur\u00fcckgegeben", + "no_still_image_or_stream_url": "Du musst mindestens eine Standbild- oder Stream-URL angeben", + "stream_file_not_found": "Datei nicht gefunden beim Versuch, eine Verbindung zum Stream herzustellen (ist ffmpeg installiert?)", + "stream_http_not_found": "HTTP 404 Not found beim Versuch, eine Verbindung zum Stream herzustellen", + "stream_io_error": "Eingabe-/Ausgabefehler beim Versuch, eine Verbindung zum Stream herzustellen. Falsches RTSP-Transportprotokoll?", + "stream_no_route_to_host": "Beim Versuch, eine Verbindung zum Stream herzustellen, konnte der Host nicht gefunden werden", + "stream_no_video": "Stream enth\u00e4lt kein Video", + "stream_not_permitted": "Beim Versuch, eine Verbindung zum Stream herzustellen, ist ein Vorgang nicht zul\u00e4ssig. Falsches RTSP-Transportprotokoll?", + "stream_unauthorised": "Autorisierung beim Versuch, eine Verbindung zum Stream herzustellen, fehlgeschlagen", + "timeout": "Zeit\u00fcberschreitung beim Laden der URL", + "unable_still_load": "Es konnte kein g\u00fcltiges Bild von der Standbild-URL geladen werden (z. B. ung\u00fcltiger Host, URL oder Authentifizierungsfehler). \u00dcberpr\u00fcfe das Protokoll f\u00fcr weitere Informationen.", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "content_type": { + "data": { + "content_type": "Inhaltstyp" + }, + "description": "Gib den Inhaltstyp des Streams an." + }, + "init": { + "data": { + "authentication": "Authentifizierung", + "content_type": "Inhaltstyp", + "framerate": "Bildfrequenz (Hz)", + "limit_refetch_to_url_change": "Neuabruf auf URL-\u00c4nderung beschr\u00e4nken", + "password": "Passwort", + "rtsp_transport": "RTSP-Transportprotokoll", + "still_image_url": "Standbild-URL (z.B. http://...)", + "stream_source": "Stream-Quell-URL (z.B. rtsp://...)", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/el.json b/homeassistant/components/generic/translations/el.json new file mode 100644 index 00000000000..f3fae37f5ca --- /dev/null +++ b/homeassistant/components/generic/translations/el.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03c3\u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "already_exists": "\u03a5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 URL.", + "invalid_still_image": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b4\u03b5\u03bd \u03b5\u03c0\u03ad\u03c3\u03c4\u03c1\u03b5\u03c8\u03b5 \u03bc\u03b9\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1", + "no_still_image_or_stream_url": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1 \u03ae \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c1\u03bf\u03ae\u03c2", + "stream_file_not_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03c1\u03bf\u03ae (\u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b5\u03c3\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf ffmpeg;)", + "stream_http_not_found": "HTTP 404 \u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae", + "stream_io_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5/\u03b5\u03be\u03cc\u03b4\u03bf\u03c5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae. \u039b\u03ac\u03b8\u03bf\u03c2 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 RTSP;", + "stream_no_route_to_host": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae", + "stream_no_video": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf", + "stream_not_permitted": "\u0394\u03b5\u03bd \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03c1\u03bf\u03ae. \u039b\u03ac\u03b8\u03bf\u03c2 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 RTSP;", + "stream_unauthorised": "\u0397 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae", + "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL", + "unable_still_load": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7\u03c2 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b1\u03ba\u03af\u03bd\u03b7\u03c4\u03b7\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2 (\u03c0.\u03c7. \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2, \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ae \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2). \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + }, + "content_type": { + "data": { + "content_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5" + }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03bf\u03ae." + }, + "user": { + "data": { + "authentication": "\u0395\u03bb\u03ad\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "content_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5", + "framerate": "\u03a1\u03c5\u03b8\u03bc\u03cc\u03c2 \u03ba\u03b1\u03c1\u03ad (Hz)", + "limit_refetch_to_url_change": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03c3\u03c4\u03b7\u03bd \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae url", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "rtsp_transport": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 RTSP", + "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2 (\u03c0.\u03c7. http://...)", + "stream_source": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c0\u03b7\u03b3\u03ae\u03c2 \u03c1\u03bf\u03ae\u03c2 (\u03c0.\u03c7. rtsp://...)", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1." + } + } + }, + "options": { + "error": { + "already_exists": "\u03a5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 URL.", + "invalid_still_image": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b4\u03b5\u03bd \u03b5\u03c0\u03ad\u03c3\u03c4\u03c1\u03b5\u03c8\u03b5 \u03bc\u03b9\u03b1 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03ba\u03af\u03bd\u03b7\u03c4\u03b7 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1", + "no_still_image_or_stream_url": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd \u03bc\u03b9\u03b1 \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1 \u03ae \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c1\u03bf\u03ae\u03c2", + "stream_file_not_found": "\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03c1\u03bf\u03ae (\u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b5\u03c3\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03c4\u03bf ffmpeg;)", + "stream_http_not_found": "HTTP 404 \u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae", + "stream_io_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5/\u03b5\u03be\u03cc\u03b4\u03bf\u03c5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae. \u039b\u03ac\u03b8\u03bf\u03c2 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 RTSP;", + "stream_no_route_to_host": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae", + "stream_no_video": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf", + "stream_not_permitted": "\u0394\u03b5\u03bd \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03c1\u03bf\u03ae. \u039b\u03ac\u03b8\u03bf\u03c2 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 RTSP;", + "stream_unauthorised": "\u0397 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03b7 \u03c1\u03bf\u03ae", + "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL", + "unable_still_load": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7\u03c2 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b1\u03ba\u03af\u03bd\u03b7\u03c4\u03b7\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2 (\u03c0.\u03c7. \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2, \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ae \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2). \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "content_type": { + "data": { + "content_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5" + }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c1\u03bf\u03ae." + }, + "init": { + "data": { + "authentication": "\u0395\u03bb\u03ad\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "content_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03bf\u03bc\u03ad\u03bd\u03bf\u03c5", + "framerate": "\u03a1\u03c5\u03b8\u03bc\u03cc\u03c2 \u03ba\u03b1\u03c1\u03ad (Hz)", + "limit_refetch_to_url_change": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03c3\u03c4\u03b7\u03bd \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae url", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "rtsp_transport": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 RTSP", + "still_image_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c3\u03c4\u03b1\u03b8\u03b5\u03c1\u03ae\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1\u03c2 (\u03c0.\u03c7. http://...)", + "stream_source": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c0\u03b7\u03b3\u03ae\u03c2 \u03c1\u03bf\u03ae\u03c2 (\u03c0.\u03c7. rtsp://...)", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/en.json b/homeassistant/components/generic/translations/en.json index 478ea76b476..b158488f178 100644 --- a/homeassistant/components/generic/translations/en.json +++ b/homeassistant/components/generic/translations/en.json @@ -32,6 +32,7 @@ "user": { "data": { "authentication": "Authentication", + "content_type": "Content Type", "framerate": "Frame Rate (Hz)", "limit_refetch_to_url_change": "Limit refetch to url change", "password": "Password", @@ -71,6 +72,7 @@ "init": { "data": { "authentication": "Authentication", + "content_type": "Content Type", "framerate": "Frame Rate (Hz)", "limit_refetch_to_url_change": "Limit refetch to url change", "password": "Password", diff --git a/homeassistant/components/generic/translations/et.json b/homeassistant/components/generic/translations/et.json new file mode 100644 index 00000000000..ae6f5aa75f8 --- /dev/null +++ b/homeassistant/components/generic/translations/et.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "error": { + "already_exists": "Nende URL-i seadetega kaamera on juba olemas.", + "invalid_still_image": "URL ei tagastanud kehtivat pilti", + "no_still_image_or_stream_url": "Pead m\u00e4\u00e4rama v\u00e4hemalt liikumatu pildi v\u00f5i voo URL-i", + "stream_file_not_found": "Vooga \u00fchenduse loomisel ei leitud faili (kas ffmpeg on installitud?)", + "stream_http_not_found": "HTTP 404 viga kui \u00fcritatakse vooga \u00fchendust luua", + "stream_io_error": "Sisend-/v\u00e4ljundviga vooga \u00fchenduse loomisel. Vale RTSP transpordiprotokoll?", + "stream_no_route_to_host": "Vooga \u00fchenduse loomisel ei leitud hosti", + "stream_no_video": "Voos pole videot", + "stream_not_permitted": "Vooga \u00fchenduse loomisel pole toiming lubatud. Vale RTSP transpordiprotokoll?", + "stream_unauthorised": "Autoriseerimine eba\u00f5nnestus vooga \u00fchendamise ajal", + "timeout": "URL-i laadimise ajal\u00f5pp", + "unable_still_load": "Pilti ei saa laadida URL-ist (nt kehtetu host, URL v\u00f5i autentimise t\u00f5rge). Lisateabe saamiseks vaata logi.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "confirm": { + "description": "Kas alustada seadistamist?" + }, + "content_type": { + "data": { + "content_type": "Sisu t\u00fc\u00fcp" + }, + "description": "M\u00e4\u00e4ra voo sisut\u00fc\u00fcp." + }, + "user": { + "data": { + "authentication": "Autentimine", + "content_type": "Sisu t\u00fc\u00fcp", + "framerate": "Kaadrisagedus (Hz)", + "limit_refetch_to_url_change": "Piira laadimist URL-i muutmiseni", + "password": "Salas\u00f5na", + "rtsp_transport": "RTSP transpordiprotokoll", + "still_image_url": "Pildi URL (nt http://...)", + "stream_source": "Voo allikas URL (nt rtsp://...)", + "username": "Kasutajanimi", + "verify_ssl": "Kontrolli SSL sertifikaati" + }, + "description": "Sisesta s\u00e4tted kaameraga \u00fchenduse loomiseks." + } + } + }, + "options": { + "error": { + "already_exists": "Nende URL-i seadetega kaamera on juba olemas.", + "invalid_still_image": "URL ei tagastanud kehtivat pilti", + "no_still_image_or_stream_url": "Pead m\u00e4\u00e4rama v\u00e4hemalt liikumatu pildi v\u00f5i voo URL-i", + "stream_file_not_found": "Vooga \u00fchenduse loomisel ei leitud faili (kas ffmpeg on installitud?)", + "stream_http_not_found": "HTTP 404 viga kui \u00fcritatakse vooga \u00fchendust luua", + "stream_io_error": "Sisend-/v\u00e4ljundviga vooga \u00fchenduse loomisel. Vale RTSP transpordiprotokoll?", + "stream_no_route_to_host": "Vooga \u00fchenduse loomisel ei leitud hosti", + "stream_no_video": "Voos pole videot", + "stream_not_permitted": "Vooga \u00fchenduse loomisel pole toiming lubatud. Vale RTSP transpordiprotokoll?", + "stream_unauthorised": "Autoriseerimine eba\u00f5nnestus vooga \u00fchendamise ajal", + "timeout": "URL-i laadimise ajal\u00f5pp", + "unable_still_load": "Pilti ei saa laadida URL-ist (nt kehtetu host, URL v\u00f5i autentimise t\u00f5rge). Lisateabe saamiseks vaata logi.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "content_type": { + "data": { + "content_type": "Sisu t\u00fc\u00fcp" + }, + "description": "M\u00e4\u00e4ra voo sisut\u00fc\u00fcp." + }, + "init": { + "data": { + "authentication": "Autentimine", + "content_type": "Sisu t\u00fc\u00fcp", + "framerate": "Kaadrisagedus (Hz)", + "limit_refetch_to_url_change": "Piira laadimist URL-i muutmiseni", + "password": "Salas\u00f5na", + "rtsp_transport": "RTSP transpordiprotokoll", + "still_image_url": "Pildi URL (nt http://...)", + "stream_source": "Voo allikas URL (nt rtsp://...)", + "username": "Kasutajanimi", + "verify_ssl": "Kontrolli SSL sertifikaati" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/fr.json b/homeassistant/components/generic/translations/fr.json new file mode 100644 index 00000000000..4905afb64e7 --- /dev/null +++ b/homeassistant/components/generic/translations/fr.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "error": { + "already_exists": "Une cam\u00e9ra avec ces param\u00e8tres d'URL existe d\u00e9j\u00e0.", + "invalid_still_image": "L'URL n'a pas renvoy\u00e9 d'image fixe valide", + "no_still_image_or_stream_url": "Vous devez au moins renseigner une URL d'image fixe ou de flux", + "stream_file_not_found": "Fichier non trouv\u00e9 lors de la tentative de connexion au flux (ffmpeg est-il install\u00e9\u00a0?)", + "stream_http_not_found": "Erreur\u00a0404 (introuvable) lors de la tentative de connexion au flux", + "stream_io_error": "Erreur d'entr\u00e9e/sortie lors de la tentative de connexion au flux. Mauvais protocole de transport RTSP\u00a0?", + "stream_no_route_to_host": "Impossible de trouver l'h\u00f4te lors de la tentative de connexion au flux", + "stream_no_video": "Le flux ne contient pas de vid\u00e9o", + "stream_not_permitted": "Op\u00e9ration non autoris\u00e9e lors de la tentative de connexion au flux. Mauvais protocole de transport RTSP\u00a0?", + "stream_unauthorised": "\u00c9chec de l'autorisation lors de la tentative de connexion au flux", + "timeout": "D\u00e9lai d'attente expir\u00e9 lors du chargement de l'URL", + "unable_still_load": "Impossible de charger une image valide depuis l'URL d'image fixe (cela pourrait \u00eatre d\u00fb \u00e0 un h\u00f4te ou \u00e0 une URL non valide, ou \u00e0 un \u00e9chec de l'authentification). Consultez le journal pour plus d'informations.", + "unknown": "Erreur inattendue" + }, + "step": { + "confirm": { + "description": "Voulez-vous commencer la configuration\u00a0?" + }, + "content_type": { + "data": { + "content_type": "Type de contenu" + }, + "description": "Sp\u00e9cifiez le type de contenu du flux." + }, + "user": { + "data": { + "authentication": "Authentification", + "content_type": "Type de contenu", + "framerate": "Fr\u00e9quence d'images (en hertz)", + "limit_refetch_to_url_change": "Limiter la r\u00e9cup\u00e9ration aux changements d'URL", + "password": "Mot de passe", + "rtsp_transport": "Protocole de transport RTSP", + "still_image_url": "URL d'image fixe (par exemple, http://\u2026)", + "stream_source": "URL de la source du flux (par exemple, rtsp://\u2026)", + "username": "Nom d'utilisateur", + "verify_ssl": "V\u00e9rifier le certificat SSL" + }, + "description": "Saisissez les param\u00e8tres de connexion \u00e0 la cam\u00e9ra." + } + } + }, + "options": { + "error": { + "already_exists": "Une cam\u00e9ra avec ces param\u00e8tres d'URL existe d\u00e9j\u00e0.", + "invalid_still_image": "L'URL n'a pas renvoy\u00e9 d'image fixe valide", + "no_still_image_or_stream_url": "Vous devez au moins renseigner une URL d'image fixe ou de flux", + "stream_file_not_found": "Fichier non trouv\u00e9 lors de la tentative de connexion au flux (ffmpeg est-il install\u00e9\u00a0?)", + "stream_http_not_found": "Erreur\u00a0404 (introuvable) lors de la tentative de connexion au flux", + "stream_io_error": "Erreur d'entr\u00e9e/sortie lors de la tentative de connexion au flux. Mauvais protocole de transport RTSP\u00a0?", + "stream_no_route_to_host": "Impossible de trouver l'h\u00f4te lors de la tentative de connexion au flux", + "stream_no_video": "Le flux ne contient pas de vid\u00e9o", + "stream_not_permitted": "Op\u00e9ration non autoris\u00e9e lors de la tentative de connexion au flux. Mauvais protocole de transport RTSP\u00a0?", + "stream_unauthorised": "\u00c9chec de l'autorisation lors de la tentative de connexion au flux", + "timeout": "D\u00e9lai d'attente expir\u00e9 lors du chargement de l'URL", + "unable_still_load": "Impossible de charger une image valide depuis l'URL d'image fixe (cela pourrait \u00eatre d\u00fb \u00e0 un h\u00f4te ou \u00e0 une URL non valide, ou \u00e0 un \u00e9chec de l'authentification). Consultez le journal pour plus d'informations.", + "unknown": "Erreur inattendue" + }, + "step": { + "content_type": { + "data": { + "content_type": "Type de contenu" + }, + "description": "Sp\u00e9cifiez le type de contenu du flux." + }, + "init": { + "data": { + "authentication": "Authentification", + "content_type": "Type de contenu", + "framerate": "Fr\u00e9quence d'images (en hertz)", + "limit_refetch_to_url_change": "Limiter la r\u00e9cup\u00e9ration aux changements d'URL", + "password": "Mot de passe", + "rtsp_transport": "Protocole de transport RTSP", + "still_image_url": "URL d'image fixe (par exemple, http://\u2026)", + "stream_source": "URL de la source du flux (par exemple, rtsp://\u2026)", + "username": "Nom d'utilisateur", + "verify_ssl": "V\u00e9rifier le certificat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/he.json b/homeassistant/components/generic/translations/he.json new file mode 100644 index 00000000000..b20b0ea88a7 --- /dev/null +++ b/homeassistant/components/generic/translations/he.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea", + "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." + }, + "error": { + "already_exists": "\u05de\u05e6\u05dc\u05de\u05d4 \u05e2\u05dd \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05d6\u05d5 \u05db\u05d1\u05e8 \u05e7\u05d9\u05d9\u05de\u05ea.", + "invalid_still_image": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8 \u05dc\u05d0 \u05d4\u05d7\u05d6\u05d9\u05e8\u05d4 \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 \u05d7\u05d5\u05e7\u05d9\u05ea", + "no_still_image_or_stream_url": "\u05d9\u05e9 \u05dc\u05e6\u05d9\u05d9\u05df \u05dc\u05e4\u05d7\u05d5\u05ea \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 \u05d0\u05d5 \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05e9\u05dc \u05d4\u05d6\u05e8\u05de\u05d4", + "stream_file_not_found": "\u05d4\u05e7\u05d5\u05d1\u05e5 \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4 (\u05d4\u05d0\u05dd ffmpeg \u05de\u05d5\u05ea\u05e7\u05df?)", + "stream_http_not_found": "HTTP 404 \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "stream_io_error": "\u05e9\u05d2\u05d9\u05d0\u05ea \u05e7\u05dc\u05d8/\u05e4\u05dc\u05d8 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4. \u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05ea\u05e2\u05d1\u05d5\u05e8\u05d4 \u05e9\u05d2\u05d5\u05d9 \u05e9\u05dc RTSP?", + "stream_no_route_to_host": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05d4\u05d9\u05d4 \u05dc\u05de\u05e6\u05d5\u05d0 \u05d0\u05ea \u05d4\u05de\u05d0\u05e8\u05d7 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "stream_no_video": "\u05d0\u05d9\u05df \u05d5\u05d9\u05d3\u05d9\u05d0\u05d5 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "stream_not_permitted": "\u05d4\u05e4\u05e2\u05d5\u05dc\u05d4 \u05d0\u05d9\u05e0\u05d4 \u05de\u05d5\u05ea\u05e8\u05ea \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4. \u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05ea\u05e2\u05d1\u05d5\u05e8\u05d4 \u05e9\u05d2\u05d5\u05d9 \u05e9\u05dc RTSP?", + "stream_unauthorised": "\u05d4\u05d4\u05e8\u05e9\u05d0\u05d4 \u05e0\u05db\u05e9\u05dc\u05d4 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "timeout": "\u05d6\u05de\u05df \u05e7\u05e6\u05d5\u05d1 \u05d1\u05e2\u05ea \u05d8\u05e2\u05d9\u05e0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8", + "unable_still_load": "\u05d0\u05d9\u05df \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d8\u05e2\u05d5\u05df \u05ea\u05de\u05d5\u05e0\u05d4 \u05d7\u05d5\u05e7\u05d9\u05ea \u05de\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8 \u05e9\u05dc \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 (\u05dc\u05d3\u05d5\u05d2\u05de\u05d4, \u05db\u05e9\u05dc \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9 \u05d1\u05de\u05d7\u05e9\u05d1 \u05de\u05d0\u05e8\u05d7, \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05d0\u05d5 \u05d0\u05d9\u05de\u05d5\u05ea). \u05e0\u05d0 \u05dc\u05e2\u05d9\u05d9\u05df \u05d1\u05d9\u05d5\u05de\u05df \u05d4\u05e8\u05d9\u05e9\u05d5\u05dd \u05dc\u05e7\u05d1\u05dc\u05ea \u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3.", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "confirm": { + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" + }, + "content_type": { + "data": { + "content_type": "\u05e1\u05d5\u05d2 \u05ea\u05d5\u05db\u05df" + }, + "description": "\u05e0\u05d0 \u05dc\u05e6\u05d9\u05d9\u05df \u05d0\u05ea \u05e1\u05d5\u05d2 \u05d4\u05ea\u05d5\u05db\u05df \u05e2\u05d1\u05d5\u05e8 \u05d4\u05d6\u05e8\u05dd." + }, + "user": { + "data": { + "authentication": "\u05d0\u05d9\u05de\u05d5\u05ea", + "content_type": "\u05e1\u05d5\u05d2 \u05ea\u05d5\u05db\u05df", + "framerate": "\u05e7\u05e6\u05d1 \u05e4\u05e8\u05d9\u05d9\u05de\u05d9\u05dd (\u05d4\u05e8\u05e5)", + "limit_refetch_to_url_change": "\u05d4\u05d2\u05d1\u05dc\u05d4 \u05e9\u05dc \u05d0\u05d7\u05e1\u05d5\u05df \u05d7\u05d5\u05d6\u05e8 \u05dc\u05e9\u05d9\u05e0\u05d5\u05d9 \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "rtsp_transport": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05ea\u05e2\u05d1\u05d5\u05e8\u05d4 RTSP", + "still_image_url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05e9\u05dc \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 (\u05dc\u05d3\u05d5\u05d2\u05de\u05d4, http://...)", + "stream_source": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05e9\u05dc \u05de\u05e7\u05d5\u05e8 \u05d6\u05e8\u05dd (\u05dc\u05d3\u05d5\u05d2\u05de\u05d4, rtsp://...)", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9", + "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" + }, + "description": "\u05e0\u05d0 \u05dc\u05d4\u05d6\u05d9\u05df \u05d0\u05ea \u05d4\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05db\u05d3\u05d9 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05de\u05e6\u05dc\u05de\u05d4." + } + } + }, + "options": { + "error": { + "already_exists": "\u05de\u05e6\u05dc\u05de\u05d4 \u05e2\u05dd \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05d6\u05d5 \u05db\u05d1\u05e8 \u05e7\u05d9\u05d9\u05de\u05ea.", + "invalid_still_image": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8 \u05dc\u05d0 \u05d4\u05d7\u05d6\u05d9\u05e8\u05d4 \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 \u05d7\u05d5\u05e7\u05d9\u05ea", + "no_still_image_or_stream_url": "\u05d9\u05e9 \u05dc\u05e6\u05d9\u05d9\u05df \u05dc\u05e4\u05d7\u05d5\u05ea \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 \u05d0\u05d5 \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05e9\u05dc \u05d4\u05d6\u05e8\u05de\u05d4", + "stream_file_not_found": "\u05d4\u05e7\u05d5\u05d1\u05e5 \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4 (\u05d4\u05d0\u05dd ffmpeg \u05de\u05d5\u05ea\u05e7\u05df?)", + "stream_http_not_found": "HTTP 404 \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "stream_io_error": "\u05e9\u05d2\u05d9\u05d0\u05ea \u05e7\u05dc\u05d8/\u05e4\u05dc\u05d8 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4. \u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05ea\u05e2\u05d1\u05d5\u05e8\u05d4 \u05e9\u05d2\u05d5\u05d9 \u05e9\u05dc RTSP?", + "stream_no_route_to_host": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05d4\u05d9\u05d4 \u05dc\u05de\u05e6\u05d5\u05d0 \u05d0\u05ea \u05d4\u05de\u05d0\u05e8\u05d7 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "stream_no_video": "\u05d0\u05d9\u05df \u05d5\u05d9\u05d3\u05d9\u05d0\u05d5 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "stream_not_permitted": "\u05d4\u05e4\u05e2\u05d5\u05dc\u05d4 \u05d0\u05d9\u05e0\u05d4 \u05de\u05d5\u05ea\u05e8\u05ea \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4. \u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05ea\u05e2\u05d1\u05d5\u05e8\u05d4 \u05e9\u05d2\u05d5\u05d9 \u05e9\u05dc RTSP?", + "stream_unauthorised": "\u05d4\u05d4\u05e8\u05e9\u05d0\u05d4 \u05e0\u05db\u05e9\u05dc\u05d4 \u05d1\u05e2\u05ea \u05e0\u05d9\u05e1\u05d9\u05d5\u05df \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05d4\u05d6\u05e8\u05de\u05d4", + "timeout": "\u05d6\u05de\u05df \u05e7\u05e6\u05d5\u05d1 \u05d1\u05e2\u05ea \u05d8\u05e2\u05d9\u05e0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8", + "unable_still_load": "\u05d0\u05d9\u05df \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d8\u05e2\u05d5\u05df \u05ea\u05de\u05d5\u05e0\u05d4 \u05d7\u05d5\u05e7\u05d9\u05ea \u05de\u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8 \u05e9\u05dc \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 (\u05dc\u05d3\u05d5\u05d2\u05de\u05d4, \u05db\u05e9\u05dc \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9 \u05d1\u05de\u05d7\u05e9\u05d1 \u05de\u05d0\u05e8\u05d7, \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05d0\u05d5 \u05d0\u05d9\u05de\u05d5\u05ea). \u05e0\u05d0 \u05dc\u05e2\u05d9\u05d9\u05df \u05d1\u05d9\u05d5\u05de\u05df \u05d4\u05e8\u05d9\u05e9\u05d5\u05dd \u05dc\u05e7\u05d1\u05dc\u05ea \u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3.", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "content_type": { + "data": { + "content_type": "\u05e1\u05d5\u05d2 \u05ea\u05d5\u05db\u05df" + }, + "description": "\u05e0\u05d0 \u05dc\u05e6\u05d9\u05d9\u05df \u05d0\u05ea \u05e1\u05d5\u05d2 \u05d4\u05ea\u05d5\u05db\u05df \u05e2\u05d1\u05d5\u05e8 \u05d4\u05d6\u05e8\u05dd." + }, + "init": { + "data": { + "authentication": "\u05d0\u05d9\u05de\u05d5\u05ea", + "content_type": "\u05e1\u05d5\u05d2 \u05ea\u05d5\u05db\u05df", + "framerate": "\u05e7\u05e6\u05d1 \u05e4\u05e8\u05d9\u05d9\u05de\u05d9\u05dd (\u05d4\u05e8\u05e5)", + "limit_refetch_to_url_change": "\u05d4\u05d2\u05d1\u05dc\u05d4 \u05e9\u05dc \u05d0\u05d7\u05e1\u05d5\u05df \u05d7\u05d5\u05d6\u05e8 \u05dc\u05e9\u05d9\u05e0\u05d5\u05d9 \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "rtsp_transport": "\u05e4\u05e8\u05d5\u05d8\u05d5\u05e7\u05d5\u05dc \u05ea\u05e2\u05d1\u05d5\u05e8\u05d4 RTSP", + "still_image_url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05e9\u05dc \u05ea\u05de\u05d5\u05e0\u05ea \u05e1\u05d8\u05d9\u05dc\u05e1 (\u05dc\u05d3\u05d5\u05d2\u05de\u05d4, http://...)", + "stream_source": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05e9\u05dc \u05de\u05e7\u05d5\u05e8 \u05d6\u05e8\u05dd (\u05dc\u05d3\u05d5\u05d2\u05de\u05d4, rtsp://...)", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9", + "verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/hu.json b/homeassistant/components/generic/translations/hu.json new file mode 100644 index 00000000000..4e97e594d1e --- /dev/null +++ b/homeassistant/components/generic/translations/hu.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "already_exists": "M\u00e1r l\u00e9tezik egy kamera ezekkel az URL-be\u00e1ll\u00edt\u00e1sokkal.", + "invalid_still_image": "Az URL nem adott vissza \u00e9rv\u00e9nyes \u00e1ll\u00f3k\u00e9pet", + "no_still_image_or_stream_url": "Legal\u00e1bb egy \u00e1ll\u00f3k\u00e9pet vagy stream URL-c\u00edmet kell megadnia.", + "stream_file_not_found": "F\u00e1jl nem tal\u00e1lhat\u00f3 a streamhez val\u00f3 csatlakoz\u00e1s sor\u00e1n (telep\u00edtve van az ffmpeg?)", + "stream_http_not_found": "HTTP 404 Not found - hiba az adatfolyamhoz val\u00f3 csatlakoz\u00e1s k\u00f6zben", + "stream_io_error": "Bemeneti/kimeneti hiba t\u00f6rt\u00e9nt az adatfolyamhoz val\u00f3 kapcsol\u00f3d\u00e1s k\u00f6zben. Rossz RTSP sz\u00e1ll\u00edt\u00e1si protokoll?", + "stream_no_route_to_host": "Nem tal\u00e1lhat\u00f3 a c\u00edm, mik\u00f6zben a rendszer az adatfolyamhoz pr\u00f3b\u00e1l csatlakozni", + "stream_no_video": "Az adatfolyamban nincs vide\u00f3", + "stream_not_permitted": "A m\u0171velet nem enged\u00e9lyezett, mik\u00f6zben megpr\u00f3b\u00e1l csatlakozni a folyamhoz. Rossz fajta RTSP protokoll?", + "stream_unauthorised": "A hiteles\u00edt\u00e9s meghi\u00fasult, mik\u00f6zben megpr\u00f3b\u00e1lt csatlakozni az adatfolyamhoz", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az URL bet\u00f6lt\u00e9se k\u00f6zben", + "unable_still_load": "Nem siker\u00fclt \u00e9rv\u00e9nyes k\u00e9pet bet\u00f6lteni az \u00e1ll\u00f3k\u00e9p URL-c\u00edm\u00e9r\u0151l (pl. \u00e9rv\u00e9nytelen host, URL vagy hiteles\u00edt\u00e9si hiba). Tov\u00e1bbi inform\u00e1ci\u00f3\u00e9rt tekintse \u00e1t a napl\u00f3t.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "confirm": { + "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" + }, + "content_type": { + "data": { + "content_type": "Tartalom t\u00edpus" + }, + "description": "Az adatfolyam tartalomt\u00edpua (Content-Type)." + }, + "user": { + "data": { + "authentication": "Hiteles\u00edt\u00e9s", + "content_type": "Tartalom t\u00edpusa", + "framerate": "K\u00e9pkockasebess\u00e9g (Hz)", + "limit_refetch_to_url_change": "Korl\u00e1tozza a visszah\u00edv\u00e1st az url v\u00e1ltoz\u00e1sra", + "password": "Jelsz\u00f3", + "rtsp_transport": "RTSP protokoll", + "still_image_url": "\u00c1ll\u00f3k\u00e9p URL (pl. http://...)", + "stream_source": "Mozg\u00f3k\u00e9p adatfolyam URL (pl. rtsp://...)", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "description": "Adja meg a kamer\u00e1hoz val\u00f3 csatlakoz\u00e1s be\u00e1ll\u00edt\u00e1sait." + } + } + }, + "options": { + "error": { + "already_exists": "M\u00e1r l\u00e9tezik egy kamera ezekkel az URL-be\u00e1ll\u00edt\u00e1sokkal.", + "invalid_still_image": "Az URL nem adott vissza \u00e9rv\u00e9nyes \u00e1ll\u00f3k\u00e9pet", + "no_still_image_or_stream_url": "Legal\u00e1bb egy \u00e1ll\u00f3k\u00e9pet vagy stream URL-c\u00edmet kell megadnia.", + "stream_file_not_found": "F\u00e1jl nem tal\u00e1lhat\u00f3 a streamhez val\u00f3 csatlakoz\u00e1s sor\u00e1n (telep\u00edtve van az ffmpeg?)", + "stream_http_not_found": "HTTP 404 Not found - hiba az adatfolyamhoz val\u00f3 csatlakoz\u00e1s k\u00f6zben", + "stream_io_error": "Bemeneti/kimeneti hiba t\u00f6rt\u00e9nt az adatfolyamhoz val\u00f3 kapcsol\u00f3d\u00e1s k\u00f6zben. Rossz RTSP sz\u00e1ll\u00edt\u00e1si protokoll?", + "stream_no_route_to_host": "Nem tal\u00e1lhat\u00f3 a c\u00edm, mik\u00f6zben a rendszer az adatfolyamhoz pr\u00f3b\u00e1l csatlakozni", + "stream_no_video": "Az adatfolyamban nincs vide\u00f3", + "stream_not_permitted": "A m\u0171velet nem enged\u00e9lyezett, mik\u00f6zben megpr\u00f3b\u00e1l csatlakozni a folyamhoz. Rossz fajta RTSP protokoll?", + "stream_unauthorised": "A hiteles\u00edt\u00e9s meghi\u00fasult, mik\u00f6zben megpr\u00f3b\u00e1lt csatlakozni az adatfolyamhoz", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az URL bet\u00f6lt\u00e9se k\u00f6zben", + "unable_still_load": "Nem siker\u00fclt \u00e9rv\u00e9nyes k\u00e9pet bet\u00f6lteni az \u00e1ll\u00f3k\u00e9p URL-c\u00edm\u00e9r\u0151l (pl. \u00e9rv\u00e9nytelen host, URL vagy hiteles\u00edt\u00e9si hiba). Tov\u00e1bbi inform\u00e1ci\u00f3\u00e9rt tekintse \u00e1t a napl\u00f3t.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "content_type": { + "data": { + "content_type": "Tartalom t\u00edpus" + }, + "description": "Az adatfolyam tartalomt\u00edpua (Content-Type)." + }, + "init": { + "data": { + "authentication": "Hiteles\u00edt\u00e9s", + "content_type": "Tartalom t\u00edpusa", + "framerate": "K\u00e9pkockasebess\u00e9g (Hz)", + "limit_refetch_to_url_change": "Korl\u00e1tozza a visszah\u00edv\u00e1st az url v\u00e1ltoz\u00e1sra", + "password": "Jelsz\u00f3", + "rtsp_transport": "RTSP protokoll", + "still_image_url": "\u00c1ll\u00f3k\u00e9p URL (pl. http://...)", + "stream_source": "Mozg\u00f3k\u00e9p adatfolyam URL (pl. rtsp://...)", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/id.json b/homeassistant/components/generic/translations/id.json new file mode 100644 index 00000000000..4b3106ad1df --- /dev/null +++ b/homeassistant/components/generic/translations/id.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "already_exists": "Kamera dengan setelan URL ini sudah ada.", + "invalid_still_image": "URL tidak mengembalikan gambar diam yang valid", + "no_still_image_or_stream_url": "Anda harus menentukan setidaknya gambar diam atau URL streaming", + "stream_file_not_found": "File tidak ditemukan saat mencoba menyambung ke streaming (sudahkah ffmpeg diinstal?)", + "stream_http_not_found": "HTTP 404 Tidak ditemukan saat mencoba menyambung ke streaming", + "stream_io_error": "Kesalahan Input/Output saat mencoba menyambung ke streaming. Apakah protokol transportasi RTSP salah?", + "stream_no_route_to_host": "Tidak dapat menemukan host saat mencoba menyambung ke streaming", + "stream_no_video": "Streaming tidak memiliki video", + "stream_not_permitted": "Operasi tidak diizinkan saat mencoba menyambung ke streaming. Apakah protokol transportasi RTSP salah?", + "stream_unauthorised": "Otorisasi gagal saat mencoba menyambung ke streaming", + "timeout": "Tenggang waktu habis saat memuat URL", + "unable_still_load": "Tidak dapat memuat gambar yang valid dari URL gambar diam (mis. host yang tidak valid, URL, atau kegagalan autentikasi). Tinjau log untuk info lebih lanjut.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + }, + "content_type": { + "data": { + "content_type": "Jenis Konten" + }, + "description": "Tentukan jenis konten untuk streaming." + }, + "user": { + "data": { + "authentication": "Autentikasi", + "content_type": "Jenis Konten", + "framerate": "Frame Rate (Hz)", + "limit_refetch_to_url_change": "Batasi pengambilan ulang untuk perubahan URL", + "password": "Kata Sandi", + "rtsp_transport": "Protokol transportasi RTSP", + "still_image_url": "URL Gambar Diam (mis. http://...)", + "stream_source": "URL Sumber Streaming (mis. rtsp://...)", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Masukkan pengaturan untuk terhubung ke kamera." + } + } + }, + "options": { + "error": { + "already_exists": "Kamera dengan setelan URL ini sudah ada.", + "invalid_still_image": "URL tidak mengembalikan gambar diam yang valid", + "no_still_image_or_stream_url": "Anda harus menentukan setidaknya gambar diam atau URL streaming", + "stream_file_not_found": "File tidak ditemukan saat mencoba menyambung ke streaming (sudahkah ffmpeg diinstal?)", + "stream_http_not_found": "HTTP 404 Tidak ditemukan saat mencoba menyambung ke streaming", + "stream_io_error": "Kesalahan Input/Output saat mencoba menyambung ke streaming. Apakah protokol transportasi RTSP salah?", + "stream_no_route_to_host": "Tidak dapat menemukan host saat mencoba menyambung ke streaming", + "stream_no_video": "Streaming tidak memiliki video", + "stream_not_permitted": "Operasi tidak diizinkan saat mencoba menyambung ke streaming. Apakah protokol transportasi RTSP salah?", + "stream_unauthorised": "Otorisasi gagal saat mencoba menyambung ke streaming", + "timeout": "Tenggang waktu habis saat memuat URL", + "unable_still_load": "Tidak dapat memuat gambar yang valid dari URL gambar diam (mis. host yang tidak valid, URL, atau kegagalan autentikasi). Tinjau log untuk info lebih lanjut.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "content_type": { + "data": { + "content_type": "Jenis Konten" + }, + "description": "Tentukan jenis konten untuk streaming." + }, + "init": { + "data": { + "authentication": "Autentikasi", + "content_type": "Jenis Konten", + "framerate": "Frame Rate (Hz)", + "limit_refetch_to_url_change": "Batasi pengambilan ulang untuk perubahan URL", + "password": "Kata Sandi", + "rtsp_transport": "Protokol transportasi RTSP", + "still_image_url": "URL Gambar Diam (mis. http://...)", + "stream_source": "URL Sumber Streaming (mis. rtsp://...)", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/it.json b/homeassistant/components/generic/translations/it.json new file mode 100644 index 00000000000..880fcff500b --- /dev/null +++ b/homeassistant/components/generic/translations/it.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "already_exists": "Esiste gi\u00e0 una telecamera con queste impostazioni URL.", + "invalid_still_image": "L'URL non ha restituito un'immagine fissa valida", + "no_still_image_or_stream_url": "Devi specificare almeno un'immagine fissa o un URL di un flusso", + "stream_file_not_found": "File non trovato durante il tentativo di connessione al (\u00e8 installato ffmpeg?)", + "stream_http_not_found": "HTTP 404 Non trovato durante il tentativo di connessione al flusso", + "stream_io_error": "Errore di input/output durante il tentativo di connessione al flusso. Protocollo di trasporto RTSP errato?", + "stream_no_route_to_host": "Impossibile trovare l'host durante il tentativo di connessione al flusso", + "stream_no_video": "Il flusso non ha video", + "stream_not_permitted": "Operazione non consentita durante il tentativo di connessione al . Protocollo di trasporto RTSP errato?", + "stream_unauthorised": "Autorizzazione non riuscita durante il tentativo di connessione al flusso", + "timeout": "Timeout durante il caricamento dell'URL", + "unable_still_load": "Impossibile caricare un'immagine valida dall'URL dell'immagine fissa (ad es. host, URL non valido o errore di autenticazione). Esamina il registro per ulteriori informazioni.", + "unknown": "Errore imprevisto" + }, + "step": { + "confirm": { + "description": "Vuoi iniziare la configurazione?" + }, + "content_type": { + "data": { + "content_type": "Tipo di contenuto" + }, + "description": "Specificare il tipo di contenuto per il flusso." + }, + "user": { + "data": { + "authentication": "Autenticazione", + "content_type": "Tipo di contenuto", + "framerate": "Frequenza fotogrammi (Hz)", + "limit_refetch_to_url_change": "Limita il recupero alla modifica dell'URL", + "password": "Password", + "rtsp_transport": "Protocollo di trasporto RTSP", + "still_image_url": "URL immagine fissa (ad es. http://...)", + "stream_source": "URL sorgente del flusso (ad es. rtsp://...)", + "username": "Nome utente", + "verify_ssl": "Verifica il certificato SSL" + }, + "description": "Inserisci le impostazioni per connetterti alla fotocamera." + } + } + }, + "options": { + "error": { + "already_exists": "Esiste gi\u00e0 una telecamera con queste impostazioni URL.", + "invalid_still_image": "L'URL non ha restituito un'immagine fissa valida", + "no_still_image_or_stream_url": "Devi specificare almeno un'immagine fissa o un URL di un flusso", + "stream_file_not_found": "File non trovato durante il tentativo di connessione al (\u00e8 installato ffmpeg?)", + "stream_http_not_found": "HTTP 404 Non trovato durante il tentativo di connessione al flusso", + "stream_io_error": "Errore di input/output durante il tentativo di connessione al flusso. Protocollo di trasporto RTSP errato?", + "stream_no_route_to_host": "Impossibile trovare l'host durante il tentativo di connessione al flusso", + "stream_no_video": "Il flusso non ha video", + "stream_not_permitted": "Operazione non consentita durante il tentativo di connessione al . Protocollo di trasporto RTSP errato?", + "stream_unauthorised": "Autorizzazione non riuscita durante il tentativo di connessione al flusso", + "timeout": "Timeout durante il caricamento dell'URL", + "unable_still_load": "Impossibile caricare un'immagine valida dall'URL dell'immagine fissa (ad es. host, URL non valido o errore di autenticazione). Esamina il registro per ulteriori informazioni.", + "unknown": "Errore imprevisto" + }, + "step": { + "content_type": { + "data": { + "content_type": "Tipo di contenuto" + }, + "description": "Specificare il tipo di contenuto per il flusso." + }, + "init": { + "data": { + "authentication": "Autenticazione", + "content_type": "Tipo di contenuto", + "framerate": "Frequenza fotogrammi (Hz)", + "limit_refetch_to_url_change": "Limita il recupero alla modifica dell'URL", + "password": "Password", + "rtsp_transport": "Protocollo di trasporto RTSP", + "still_image_url": "URL immagine fissa (ad es. http://...)", + "stream_source": "URL sorgente del flusso (ad es. rtsp://...)", + "username": "Nome utente", + "verify_ssl": "Verifica il certificato SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/ja.json b/homeassistant/components/generic/translations/ja.json new file mode 100644 index 00000000000..916142125e6 --- /dev/null +++ b/homeassistant/components/generic/translations/ja.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093", + "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" + }, + "error": { + "already_exists": "\u3053\u306eURL\u8a2d\u5b9a\u306e\u30ab\u30e1\u30e9\u306f\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059\u3002", + "invalid_still_image": "URL\u304c\u6709\u52b9\u306a\u9759\u6b62\u753b\u50cf\u3092\u8fd4\u3057\u307e\u305b\u3093\u3067\u3057\u305f", + "no_still_image_or_stream_url": "\u9759\u6b62\u753b\u50cf\u3082\u3057\u304f\u306f\u3001\u30b9\u30c8\u30ea\u30fc\u30e0URL\u306e\u3069\u3061\u3089\u304b\u3092\u6307\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "stream_file_not_found": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u3068\u304d\u306b\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093(ffmpeg\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u307e\u3059\u304b\uff1f)", + "stream_http_not_found": "\u30b9\u30c8\u30ea\u30fc\u30e0\u3078\u306e\u63a5\u7d9a\u6642\u306b\u3001HTTP 404\u3067\u898b\u3064\u304b\u308a\u307e\u305b\u3093", + "stream_io_error": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u305f\u3068\u304d\u306b\u5165\u51fa\u529b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002RTSP\u30c8\u30e9\u30f3\u30b9\u30dd\u30fc\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb\u3092\u9593\u9055\u3048\u305f\uff1f", + "stream_no_route_to_host": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\u304c\u3001\u30db\u30b9\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "stream_no_video": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u52d5\u753b\u304c\u3042\u308a\u307e\u305b\u3093", + "stream_not_permitted": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u9593\u3001\u64cd\u4f5c\u3067\u304d\u307e\u305b\u3093\u3002RTSP\u30c8\u30e9\u30f3\u30b9\u30dd\u30fc\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb\u3092\u9593\u9055\u3048\u305f\uff1f", + "stream_unauthorised": "\u30b9\u30c8\u30ea\u30fc\u30e0\u3078\u306e\u63a5\u7d9a\u6642\u306b\u3001\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "timeout": "URL\u306e\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8", + "unable_still_load": "\u9759\u6b62\u753b\u306eURL\u304b\u3089\u6709\u52b9\u306a\u753b\u50cf\u3092\u8aad\u307f\u8fbc\u3080\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\uff08\u4f8b: \u7121\u52b9\u306a\u30db\u30b9\u30c8\u3001URL\u3001\u307e\u305f\u306f\u8a8d\u8a3c\u5931\u6557)\u3002\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "confirm": { + "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" + }, + "content_type": { + "data": { + "content_type": "\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e" + }, + "description": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u30bf\u30a4\u30d7\u3092\u6307\u5b9a\u3057\u307e\u3059\u3002" + }, + "user": { + "data": { + "authentication": "\u8a8d\u8a3c", + "content_type": "\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e", + "framerate": "\u30d5\u30ec\u30fc\u30e0\u30ec\u30fc\u30c8\uff08Hz\uff09", + "limit_refetch_to_url_change": "URL\u5909\u66f4\u6642\u306e\u518d\u53d6\u5f97\u3092\u5236\u9650\u3059\u308b", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "rtsp_transport": "RTSP\u30c8\u30e9\u30f3\u30b9\u30dd\u30fc\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb", + "still_image_url": "\u9759\u6b62\u753b\u50cf\u306eURL(\u4f8b: http://...)", + "stream_source": "\u30b9\u30c8\u30ea\u30fc\u30e0\u30bd\u30fc\u30b9\u306eURL(\u4f8b: rtsp://...)", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d", + "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + }, + "description": "\u30ab\u30e1\u30e9\u306b\u63a5\u7d9a\u3059\u308b\u305f\u3081\u306e\u8a2d\u5b9a\u3092\u5165\u529b\u3057\u307e\u3059\u3002" + } + } + }, + "options": { + "error": { + "already_exists": "\u3053\u306eURL\u8a2d\u5b9a\u306e\u30ab\u30e1\u30e9\u306f\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059\u3002", + "invalid_still_image": "URL\u304c\u6709\u52b9\u306a\u9759\u6b62\u753b\u50cf\u3092\u8fd4\u3057\u307e\u305b\u3093\u3067\u3057\u305f", + "no_still_image_or_stream_url": "\u9759\u6b62\u753b\u50cf\u3082\u3057\u304f\u306f\u3001\u30b9\u30c8\u30ea\u30fc\u30e0URL\u306e\u3069\u3061\u3089\u304b\u3092\u6307\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "stream_file_not_found": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u3068\u304d\u306b\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093(ffmpeg\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u307e\u3059\u304b\uff1f)", + "stream_http_not_found": "\u30b9\u30c8\u30ea\u30fc\u30e0\u3078\u306e\u63a5\u7d9a\u6642\u306b\u3001HTTP 404\u3067\u898b\u3064\u304b\u308a\u307e\u305b\u3093", + "stream_io_error": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u305f\u3068\u304d\u306b\u5165\u51fa\u529b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002RTSP\u30c8\u30e9\u30f3\u30b9\u30dd\u30fc\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb\u3092\u9593\u9055\u3048\u305f\uff1f", + "stream_no_route_to_host": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\u304c\u3001\u30db\u30b9\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "stream_no_video": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u52d5\u753b\u304c\u3042\u308a\u307e\u305b\u3093", + "stream_not_permitted": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u9593\u3001\u64cd\u4f5c\u3067\u304d\u307e\u305b\u3093\u3002RTSP\u30c8\u30e9\u30f3\u30b9\u30dd\u30fc\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb\u3092\u9593\u9055\u3048\u305f\uff1f", + "stream_unauthorised": "\u30b9\u30c8\u30ea\u30fc\u30e0\u3078\u306e\u63a5\u7d9a\u6642\u306b\u3001\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "timeout": "URL\u306e\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8", + "unable_still_load": "\u9759\u6b62\u753b\u306eURL\u304b\u3089\u6709\u52b9\u306a\u753b\u50cf\u3092\u8aad\u307f\u8fbc\u3080\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\uff08\u4f8b: \u7121\u52b9\u306a\u30db\u30b9\u30c8\u3001URL\u3001\u307e\u305f\u306f\u8a8d\u8a3c\u5931\u6557)\u3002\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "content_type": { + "data": { + "content_type": "\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e" + }, + "description": "\u30b9\u30c8\u30ea\u30fc\u30e0\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u30bf\u30a4\u30d7\u3092\u6307\u5b9a\u3057\u307e\u3059\u3002" + }, + "init": { + "data": { + "authentication": "\u8a8d\u8a3c", + "content_type": "\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e", + "framerate": "\u30d5\u30ec\u30fc\u30e0\u30ec\u30fc\u30c8\uff08Hz\uff09", + "limit_refetch_to_url_change": "URL\u5909\u66f4\u6642\u306e\u518d\u53d6\u5f97\u3092\u5236\u9650\u3059\u308b", + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "rtsp_transport": "RTSP\u30c8\u30e9\u30f3\u30b9\u30dd\u30fc\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb", + "still_image_url": "\u9759\u6b62\u753b\u50cf\u306eURL(\u4f8b: http://...)", + "stream_source": "\u30b9\u30c8\u30ea\u30fc\u30e0\u30bd\u30fc\u30b9\u306eURL(\u4f8b: rtsp://...)", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d", + "verify_ssl": "SSL\u8a3c\u660e\u66f8\u3092\u78ba\u8a8d\u3059\u308b" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/nl.json b/homeassistant/components/generic/translations/nl.json new file mode 100644 index 00000000000..167374c7aeb --- /dev/null +++ b/homeassistant/components/generic/translations/nl.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "already_exists": "Een camera met deze URL instellingen bestaat al.", + "invalid_still_image": "URL heeft geen geldig stilstaand beeld geretourneerd", + "no_still_image_or_stream_url": "U moet ten minste een stilstaand beeld of stream-URL specificeren", + "stream_file_not_found": "Bestand niet gevonden tijdens verbinding met stream (is ffmpeg ge\u00efnstalleerd?)", + "stream_http_not_found": "HTTP 404 Niet gevonden bij poging om verbinding te maken met stream", + "stream_io_error": "Input/Output fout bij het proberen te verbinden met stream. Verkeerde RTSP transport protocol?", + "stream_no_route_to_host": "Kan de host niet vinden terwijl u verbinding probeert te maken met de stream", + "stream_no_video": "Stream heeft geen video", + "stream_not_permitted": "Operatie niet toegestaan bij poging om verbinding te maken met stream. Verkeerd RTSP transport protocol?", + "stream_unauthorised": "Autorisatie mislukt bij poging om verbinding te maken met stream", + "timeout": "Time-out tijdens het laden van URL", + "unable_still_load": "Kan geen geldige afbeelding laden van stilstaande afbeelding URL (b.v. ongeldige host, URL of authenticatie fout). Bekijk het log voor meer informatie.", + "unknown": "Onverwachte fout" + }, + "step": { + "confirm": { + "description": "Wilt u beginnen met instellen?" + }, + "content_type": { + "data": { + "content_type": "Inhoudstype" + }, + "description": "Geef het inhoudstype voor de stream op." + }, + "user": { + "data": { + "authentication": "Authenticatie", + "content_type": "Inhoudstype", + "framerate": "Framesnelheid (Hz)", + "limit_refetch_to_url_change": "Beperk refetch tot url verandering", + "password": "Wachtwoord", + "rtsp_transport": "RTSP-transportprotocol", + "still_image_url": "URL van stilstaande afbeelding (bijv. http://...)", + "stream_source": "Url van streambron (bijv. rtsp://...)", + "username": "Gebruikersnaam", + "verify_ssl": "SSL-certificaat verifi\u00ebren" + }, + "description": "Voer de instellingen in om verbinding te maken met de camera." + } + } + }, + "options": { + "error": { + "already_exists": "Een camera met deze URL instellingen bestaat al.", + "invalid_still_image": "URL heeft geen geldig stilstaand beeld geretourneerd", + "no_still_image_or_stream_url": "U moet ten minste een stilstaand beeld of stream-URL specificeren", + "stream_file_not_found": "Bestand niet gevonden tijdens verbinding met stream (is ffmpeg ge\u00efnstalleerd?)", + "stream_http_not_found": "HTTP 404 Niet gevonden bij poging om verbinding te maken met stream", + "stream_io_error": "Input/Output fout bij het proberen te verbinden met stream. Verkeerde RTSP transport protocol?", + "stream_no_route_to_host": "Kan de host niet vinden terwijl u verbinding probeert te maken met de stream", + "stream_no_video": "Stream heeft geen video", + "stream_not_permitted": "Operatie niet toegestaan bij poging om verbinding te maken met stream. Verkeerd RTSP transport protocol?", + "stream_unauthorised": "Autorisatie mislukt bij poging om verbinding te maken met stream", + "timeout": "Time-out tijdens het laden van URL", + "unable_still_load": "Kan geen geldige afbeelding laden van stilstaande afbeelding URL (b.v. ongeldige host, URL of authenticatie fout). Bekijk het log voor meer informatie.", + "unknown": "Onverwachte fout" + }, + "step": { + "content_type": { + "data": { + "content_type": "Inhoudstype" + }, + "description": "Geef het inhoudstype voor de stream op." + }, + "init": { + "data": { + "authentication": "Authenticatie", + "content_type": "Inhoudstype", + "framerate": "Framesnelheid (Hz)", + "limit_refetch_to_url_change": "Beperk refetch tot url verandering", + "password": "Wachtwoord", + "rtsp_transport": "RTSP-transportprotocol", + "still_image_url": "URL van stilstaande afbeelding (bijv. http://...)", + "stream_source": "Url van streambron (bijv. rtsp://...)", + "username": "Gebruikersnaam", + "verify_ssl": "SSL-certificaat verifi\u00ebren" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/no.json b/homeassistant/components/generic/translations/no.json new file mode 100644 index 00000000000..1f9eedaced4 --- /dev/null +++ b/homeassistant/components/generic/translations/no.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "already_exists": "Et kamera med disse URL-innstillingene finnes allerede.", + "invalid_still_image": "URL returnerte ikke et gyldig stillbilde", + "no_still_image_or_stream_url": "Du m\u00e5 angi minst en URL-adresse for stillbilde eller dataflyt", + "stream_file_not_found": "Filen ble ikke funnet under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8m (er ffmpeg installert?)", + "stream_http_not_found": "HTTP 404 Ikke funnet under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8m", + "stream_io_error": "Inn-/utdatafeil under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8m. Feil RTSP-transportprotokoll?", + "stream_no_route_to_host": "Kunne ikke finne verten under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8mmen", + "stream_no_video": "Stream har ingen video", + "stream_not_permitted": "Operasjon er ikke tillatt mens du pr\u00f8ver \u00e5 koble til str\u00f8m. Feil RTSP-transportprotokoll?", + "stream_unauthorised": "Autorisasjonen mislyktes under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8mmen", + "timeout": "Tidsavbrudd under innlasting av URL", + "unable_still_load": "Kan ikke laste inn gyldig bilde fra URL-adresse for stillbilde (f.eks. ugyldig verts-, URL- eller godkjenningsfeil). Se gjennom loggen hvis du vil ha mer informasjon.", + "unknown": "Uventet feil" + }, + "step": { + "confirm": { + "description": "Vil du starte oppsettet?" + }, + "content_type": { + "data": { + "content_type": "Innholdstype" + }, + "description": "Angi innholdstypen for str\u00f8mmen." + }, + "user": { + "data": { + "authentication": "Godkjenning", + "content_type": "Innholdstype", + "framerate": "Bildefrekvens (Hz)", + "limit_refetch_to_url_change": "Begrens gjenhenting til endring av nettadresse", + "password": "Passord", + "rtsp_transport": "RTSP transportprotokoll", + "still_image_url": "Stillbilde-URL (f.eks. http://...)", + "stream_source": "Str\u00f8mkilde-URL (f.eks. rtsp://...)", + "username": "Brukernavn", + "verify_ssl": "Verifisere SSL-sertifikat" + }, + "description": "Angi innstillingene for \u00e5 koble til kameraet." + } + } + }, + "options": { + "error": { + "already_exists": "Et kamera med disse URL-innstillingene finnes allerede.", + "invalid_still_image": "URL returnerte ikke et gyldig stillbilde", + "no_still_image_or_stream_url": "Du m\u00e5 angi minst en URL-adresse for stillbilde eller dataflyt", + "stream_file_not_found": "Filen ble ikke funnet under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8m (er ffmpeg installert?)", + "stream_http_not_found": "HTTP 404 Ikke funnet under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8m", + "stream_io_error": "Inn-/utdatafeil under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8m. Feil RTSP-transportprotokoll?", + "stream_no_route_to_host": "Kunne ikke finne verten under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8mmen", + "stream_no_video": "Stream har ingen video", + "stream_not_permitted": "Operasjon er ikke tillatt mens du pr\u00f8ver \u00e5 koble til str\u00f8m. Feil RTSP-transportprotokoll?", + "stream_unauthorised": "Autorisasjonen mislyktes under fors\u00f8k p\u00e5 \u00e5 koble til str\u00f8mmen", + "timeout": "Tidsavbrudd under innlasting av URL", + "unable_still_load": "Kan ikke laste inn gyldig bilde fra URL-adresse for stillbilde (f.eks. ugyldig verts-, URL- eller godkjenningsfeil). Se gjennom loggen hvis du vil ha mer informasjon.", + "unknown": "Uventet feil" + }, + "step": { + "content_type": { + "data": { + "content_type": "Innholdstype" + }, + "description": "Angi innholdstypen for str\u00f8mmen." + }, + "init": { + "data": { + "authentication": "Godkjenning", + "content_type": "Innholdstype", + "framerate": "Bildefrekvens (Hz)", + "limit_refetch_to_url_change": "Begrens gjenhenting til endring av nettadresse", + "password": "Passord", + "rtsp_transport": "RTSP transportprotokoll", + "still_image_url": "Stillbilde-URL (f.eks. http://...)", + "stream_source": "Str\u00f8mkilde-URL (f.eks. rtsp://...)", + "username": "Brukernavn", + "verify_ssl": "Verifisere SSL-sertifikat" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/pl.json b/homeassistant/components/generic/translations/pl.json new file mode 100644 index 00000000000..a791a56c065 --- /dev/null +++ b/homeassistant/components/generic/translations/pl.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "already_exists": "Kamera z tymi ustawieniami adresu URL ju\u017c istnieje.", + "invalid_still_image": "Adres URL nie zwr\u00f3ci\u0142 prawid\u0142owego obrazu nieruchomego (still image)", + "no_still_image_or_stream_url": "Musisz poda\u0107 przynajmniej nieruchomy obraz (still image) lub adres URL strumienia", + "stream_file_not_found": "Nie znaleziono pliku podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem (czy ffmpeg jest zainstalowany?)", + "stream_http_not_found": "\"HTTP 404 Nie znaleziono\" podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem", + "stream_io_error": "B\u0142\u0105d wej\u015bcia/wyj\u015bcia podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem. Z\u0142y protok\u00f3\u0142 transportowy RTSP?", + "stream_no_route_to_host": "Nie mo\u017cna znale\u017a\u0107 hosta podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem", + "stream_no_video": "Strumie\u0144 nie zawiera wideo", + "stream_not_permitted": "Operacja nie jest dozwolona podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem. Z\u0142y protok\u00f3\u0142 transportowy RTSP?", + "stream_unauthorised": "Autoryzacja nie powiod\u0142a si\u0119 podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem", + "timeout": "Przekroczono limit czasu podczas \u0142adowania adresu URL", + "unable_still_load": "Nie mo\u017cna za\u0142adowa\u0107 prawid\u0142owego obrazu z adresu URL nieruchomego obrazu (np. nieprawid\u0142owy host, adres URL lub b\u0142\u0105d uwierzytelniania). Przejrzyj logi, aby uzyska\u0107 wi\u0119cej informacji.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "confirm": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + }, + "content_type": { + "data": { + "content_type": "Typ zawarto\u015bci" + }, + "description": "Okre\u015bl typ zawarto\u015bci strumienia." + }, + "user": { + "data": { + "authentication": "Uwierzytelnianie", + "content_type": "Typ zawarto\u015bci", + "framerate": "Od\u015bwie\u017canie (Hz)", + "limit_refetch_to_url_change": "Ogranicz pobieranie do zmiany adresu URL", + "password": "Has\u0142o", + "rtsp_transport": "Protok\u00f3\u0142 transportowy RTSP", + "still_image_url": "Adres URL obrazu nieruchomego (np. http://...)", + "stream_source": "Adres URL strumienia (np. rtsp://...)", + "username": "Nazwa u\u017cytkownika", + "verify_ssl": "Weryfikacja certyfikatu SSL" + }, + "description": "Wprowad\u017a ustawienia, aby po\u0142\u0105czy\u0107 si\u0119 z kamer\u0105." + } + } + }, + "options": { + "error": { + "already_exists": "Kamera z tymi ustawieniami adresu URL ju\u017c istnieje.", + "invalid_still_image": "Adres URL nie zwr\u00f3ci\u0142 prawid\u0142owego obrazu nieruchomego (still image)", + "no_still_image_or_stream_url": "Musisz poda\u0107 przynajmniej nieruchomy obraz (still image) lub adres URL strumienia", + "stream_file_not_found": "Nie znaleziono pliku podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem (czy ffmpeg jest zainstalowany?)", + "stream_http_not_found": "\"HTTP 404 Nie znaleziono\" podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem", + "stream_io_error": "B\u0142\u0105d wej\u015bcia/wyj\u015bcia podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem. Z\u0142y protok\u00f3\u0142 transportowy RTSP?", + "stream_no_route_to_host": "Nie mo\u017cna znale\u017a\u0107 hosta podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem", + "stream_no_video": "Strumie\u0144 nie zawiera wideo", + "stream_not_permitted": "Operacja nie jest dozwolona podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem. Z\u0142y protok\u00f3\u0142 transportowy RTSP?", + "stream_unauthorised": "Autoryzacja nie powiod\u0142a si\u0119 podczas pr\u00f3by po\u0142\u0105czenia ze strumieniem", + "timeout": "Przekroczono limit czasu podczas \u0142adowania adresu URL", + "unable_still_load": "Nie mo\u017cna za\u0142adowa\u0107 prawid\u0142owego obrazu z adresu URL nieruchomego obrazu (np. nieprawid\u0142owy host, adres URL lub b\u0142\u0105d uwierzytelniania). Przejrzyj logi, aby uzyska\u0107 wi\u0119cej informacji.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "content_type": { + "data": { + "content_type": "Typ zawarto\u015bci" + }, + "description": "Okre\u015bl typ zawarto\u015bci strumienia." + }, + "init": { + "data": { + "authentication": "Uwierzytelnianie", + "content_type": "Typ zawarto\u015bci", + "framerate": "Od\u015bwie\u017canie (Hz)", + "limit_refetch_to_url_change": "Ogranicz pobieranie do zmiany adresu URL", + "password": "Has\u0142o", + "rtsp_transport": "Protok\u00f3\u0142 transportowy RTSP", + "still_image_url": "Adres URL obrazu nieruchomego (np. http://...)", + "stream_source": "Adres URL strumienia (np. rtsp://...)", + "username": "Nazwa u\u017cytkownika", + "verify_ssl": "Weryfikacja certyfikatu SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/pt-BR.json b/homeassistant/components/generic/translations/pt-BR.json new file mode 100644 index 00000000000..0e3ce2bd75d --- /dev/null +++ b/homeassistant/components/generic/translations/pt-BR.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nenhum dispositivo encontrado na rede", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "already_exists": "J\u00e1 existe uma c\u00e2mera com essas configura\u00e7\u00f5es de URL.", + "invalid_still_image": "A URL n\u00e3o retornou uma imagem est\u00e1tica v\u00e1lida", + "no_still_image_or_stream_url": "Voc\u00ea deve especificar pelo menos uma imagem est\u00e1tica ou uma URL de stream", + "stream_file_not_found": "Arquivo n\u00e3o encontrado ao tentar se conectar a stream (o ffmpeg est\u00e1 instalado?)", + "stream_http_not_found": "HTTP 404 n\u00e3o encontrado ao tentar se conectar a stream", + "stream_io_error": "Erro de entrada/sa\u00edda ao tentar se conectar a stream. Protocolo RTSP errado?", + "stream_no_route_to_host": "N\u00e3o foi poss\u00edvel encontrar o host ao tentar se conectar a stream", + "stream_no_video": "A stream n\u00e3o tem v\u00eddeo", + "stream_not_permitted": "Opera\u00e7\u00e3o n\u00e3o permitida ao tentar se conectar a stream. Protocolo RTSP errado?", + "stream_unauthorised": "Falha na autoriza\u00e7\u00e3o ao tentar se conectar a stream", + "timeout": "Tempo limite ao carregar a URL", + "unable_still_load": "N\u00e3o foi poss\u00edvel carregar uma imagem v\u00e1lida do URL da imagem est\u00e1tica (por exemplo, host inv\u00e1lido, URL ou falha de autentica\u00e7\u00e3o). Revise o log para obter mais informa\u00e7\u00f5es.", + "unknown": "Erro inesperado" + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + }, + "content_type": { + "data": { + "content_type": "Tipo de conte\u00fado" + }, + "description": "Especifique o tipo de conte\u00fado para o stream." + }, + "user": { + "data": { + "authentication": "Autentica\u00e7\u00e3o", + "content_type": "Tipo de conte\u00fado", + "framerate": "Taxa de quadros (Hz)", + "limit_refetch_to_url_change": "Limitar a nova busca \u00e0 altera\u00e7\u00e3o de URL", + "password": "Senha", + "rtsp_transport": "Protocolo RTSP", + "still_image_url": "URL da imagem est\u00e1tica (por exemplo, http://...)", + "stream_source": "URL de origem da Stream (por exemplo, rtsp://...)", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" + }, + "description": "Insira as configura\u00e7\u00f5es para se conectar \u00e0 c\u00e2mera." + } + } + }, + "options": { + "error": { + "already_exists": "J\u00e1 existe uma c\u00e2mera com essas configura\u00e7\u00f5es de URL.", + "invalid_still_image": "A URL n\u00e3o retornou uma imagem est\u00e1tica v\u00e1lida", + "no_still_image_or_stream_url": "Voc\u00ea deve especificar pelo menos uma imagem est\u00e1tica ou uma URL de stream", + "stream_file_not_found": "Arquivo n\u00e3o encontrado ao tentar se conectar a stream (o ffmpeg est\u00e1 instalado?)", + "stream_http_not_found": "HTTP 404 n\u00e3o encontrado ao tentar se conectar a stream", + "stream_io_error": "Erro de entrada/sa\u00edda ao tentar se conectar a stream. Protocolo RTSP errado?", + "stream_no_route_to_host": "N\u00e3o foi poss\u00edvel encontrar o host ao tentar se conectar a stream", + "stream_no_video": "A stream n\u00e3o tem v\u00eddeo", + "stream_not_permitted": "Opera\u00e7\u00e3o n\u00e3o permitida ao tentar se conectar a stream. Protocolo RTSP errado?", + "stream_unauthorised": "Falha na autoriza\u00e7\u00e3o ao tentar se conectar a stream", + "timeout": "Tempo limite ao carregar a URL", + "unable_still_load": "N\u00e3o foi poss\u00edvel carregar uma imagem v\u00e1lida do URL da imagem est\u00e1tica (por exemplo, host inv\u00e1lido, URL ou falha de autentica\u00e7\u00e3o). Revise o log para obter mais informa\u00e7\u00f5es.", + "unknown": "Erro inesperado" + }, + "step": { + "content_type": { + "data": { + "content_type": "Tipo de conte\u00fado" + }, + "description": "Especifique o tipo de conte\u00fado para o stream." + }, + "init": { + "data": { + "authentication": "Autentica\u00e7\u00e3o", + "content_type": "Tipo de conte\u00fado", + "framerate": "Taxa de quadros (Hz)", + "limit_refetch_to_url_change": "Limitar a nova busca \u00e0 altera\u00e7\u00e3o de URL", + "password": "Senha", + "rtsp_transport": "Protocolo RTSP", + "still_image_url": "URL da imagem est\u00e1tica (por exemplo, http://...)", + "stream_source": "URL de origem da Stream (por exemplo, rtsp://...)", + "username": "Usu\u00e1rio", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/ru.json b/homeassistant/components/generic/translations/ru.json new file mode 100644 index 00000000000..06f41613a50 --- /dev/null +++ b/homeassistant/components/generic/translations/ru.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." + }, + "error": { + "already_exists": "\u041a\u0430\u043c\u0435\u0440\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c URL-\u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.", + "invalid_still_image": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u043e\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435.", + "no_still_image_or_stream_url": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043f\u043e\u0442\u043e\u043a\u0430.", + "stream_file_not_found": "\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043b\u0438 ffmpeg?", + "stream_http_not_found": "HTTP 404 \u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443.", + "stream_io_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0432\u043e\u0434\u0430/\u0432\u044b\u0432\u043e\u0434\u0430 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b RTSP?", + "stream_no_route_to_host": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0445\u043e\u0441\u0442 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443.", + "stream_no_video": "\u0412 \u043f\u043e\u0442\u043e\u043a\u0435 \u043d\u0435\u0442 \u0432\u0438\u0434\u0435\u043e.", + "stream_not_permitted": "\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b RTSP?", + "stream_unauthorised": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443.", + "timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 URL-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unable_still_load": "\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441 URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (\u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u043e\u0441\u0442, URL-\u0430\u0434\u0440\u0435\u0441 \u0438\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438). \u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0436\u0443\u0440\u043d\u0430\u043b \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" + }, + "content_type": { + "data": { + "content_type": "\u0422\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e" + }, + "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u0442\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0434\u043b\u044f \u043f\u043e\u0442\u043e\u043a\u0430." + }, + "user": { + "data": { + "authentication": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f", + "content_type": "\u0422\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e", + "framerate": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043a\u0430\u0434\u0440\u043e\u0432 (\u0413\u0446)", + "limit_refetch_to_url_change": "\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "rtsp_transport": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b RTSP", + "still_image_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, http://...)", + "stream_source": "URL-\u0430\u0434\u0440\u0435\u0441 \u043f\u043e\u0442\u043e\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, rtsp://...)", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043a\u0430\u043c\u0435\u0440\u0435." + } + } + }, + "options": { + "error": { + "already_exists": "\u041a\u0430\u043c\u0435\u0440\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c URL-\u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.", + "invalid_still_image": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u043e\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435.", + "no_still_image_or_stream_url": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043f\u043e\u0442\u043e\u043a\u0430.", + "stream_file_not_found": "\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043b\u0438 ffmpeg?", + "stream_http_not_found": "HTTP 404 \u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443.", + "stream_io_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0432\u043e\u0434\u0430/\u0432\u044b\u0432\u043e\u0434\u0430 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b RTSP?", + "stream_no_route_to_host": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0445\u043e\u0441\u0442 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443.", + "stream_no_video": "\u0412 \u043f\u043e\u0442\u043e\u043a\u0435 \u043d\u0435\u0442 \u0432\u0438\u0434\u0435\u043e.", + "stream_not_permitted": "\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b RTSP?", + "stream_unauthorised": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043f\u043e\u0442\u043e\u043a\u0443.", + "timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 URL-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unable_still_load": "\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441 URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (\u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u043e\u0441\u0442, URL-\u0430\u0434\u0440\u0435\u0441 \u0438\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438). \u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0436\u0443\u0440\u043d\u0430\u043b \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "content_type": { + "data": { + "content_type": "\u0422\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e" + }, + "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u0442\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0434\u043b\u044f \u043f\u043e\u0442\u043e\u043a\u0430." + }, + "init": { + "data": { + "authentication": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f", + "content_type": "\u0422\u0438\u043f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e", + "framerate": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043a\u0430\u0434\u0440\u043e\u0432 (\u0413\u0446)", + "limit_refetch_to_url_change": "\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "rtsp_transport": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b RTSP", + "still_image_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, http://...)", + "stream_source": "URL-\u0430\u0434\u0440\u0435\u0441 \u043f\u043e\u0442\u043e\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, rtsp://...)", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/tr.json b/homeassistant/components/generic/translations/tr.json new file mode 100644 index 00000000000..c2fb343f227 --- /dev/null +++ b/homeassistant/components/generic/translations/tr.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "A\u011fda cihaz bulunamad\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "already_exists": "Bu URL ayarlar\u0131na sahip bir kamera zaten var.", + "invalid_still_image": "URL ge\u00e7erli bir hareketsiz resim d\u00f6nd\u00fcrmedi", + "no_still_image_or_stream_url": "En az\u0131ndan bir dura\u011fan resim veya ak\u0131\u015f URL'si belirtmelisiniz", + "stream_file_not_found": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken dosya bulunamad\u0131 (ffmpeg y\u00fckl\u00fc m\u00fc?)", + "stream_http_not_found": "HTTP 404 Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken bulunamad\u0131", + "stream_io_error": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken Giri\u015f/\u00c7\u0131k\u0131\u015f hatas\u0131. Yanl\u0131\u015f RTSP aktar\u0131m protokol\u00fc?", + "stream_no_route_to_host": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken ana bilgisayar bulunamad\u0131", + "stream_no_video": "Ak\u0131\u015fta video yok", + "stream_not_permitted": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken i\u015fleme izin verilmiyor. Yanl\u0131\u015f RTSP aktar\u0131m protokol\u00fc?", + "stream_unauthorised": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken yetkilendirme ba\u015far\u0131s\u0131z oldu", + "timeout": "URL y\u00fcklenirken zaman a\u015f\u0131m\u0131", + "unable_still_load": "Hareketsiz resim URL'sinden ge\u00e7erli resim y\u00fcklenemiyor (\u00f6r. ge\u00e7ersiz ana bilgisayar, URL veya kimlik do\u011frulama hatas\u0131). Daha fazla bilgi i\u00e7in g\u00fcnl\u00fc\u011f\u00fc inceleyin.", + "unknown": "Beklenmeyen hata" + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + }, + "content_type": { + "data": { + "content_type": "\u0130\u00e7erik T\u00fcr\u00fc" + }, + "description": "Ak\u0131\u015f i\u00e7in i\u00e7erik t\u00fcr\u00fcn\u00fc belirtin." + }, + "user": { + "data": { + "authentication": "Kimlik Do\u011frulama", + "content_type": "\u0130\u00e7erik T\u00fcr\u00fc", + "framerate": "Kare H\u0131z\u0131 (Hz)", + "limit_refetch_to_url_change": "Yeniden getirmeyi url de\u011fi\u015fikli\u011fiyle s\u0131n\u0131rla", + "password": "Parola", + "rtsp_transport": "RTSP aktar\u0131m protokol\u00fc", + "still_image_url": "Hareketsiz G\u00f6r\u00fcnt\u00fc URL'si (\u00f6rne\u011fin http://...)", + "stream_source": "Ak\u0131\u015f Kayna\u011f\u0131 URL'si (\u00f6r. rtsp://...)", + "username": "Kullan\u0131c\u0131 Ad\u0131", + "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + }, + "description": "Kameraya ba\u011flanmak i\u00e7in ayarlar\u0131 girin." + } + } + }, + "options": { + "error": { + "already_exists": "Bu URL ayarlar\u0131na sahip bir kamera zaten var.", + "invalid_still_image": "URL ge\u00e7erli bir hareketsiz resim d\u00f6nd\u00fcrmedi", + "no_still_image_or_stream_url": "En az\u0131ndan bir dura\u011fan resim veya ak\u0131\u015f URL'si belirtmelisiniz", + "stream_file_not_found": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken dosya bulunamad\u0131 (ffmpeg y\u00fckl\u00fc m\u00fc?)", + "stream_http_not_found": "HTTP 404 Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken bulunamad\u0131", + "stream_io_error": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken Giri\u015f/\u00c7\u0131k\u0131\u015f hatas\u0131. Yanl\u0131\u015f RTSP aktar\u0131m protokol\u00fc?", + "stream_no_route_to_host": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken ana bilgisayar bulunamad\u0131", + "stream_no_video": "Ak\u0131\u015fta video yok", + "stream_not_permitted": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken i\u015fleme izin verilmiyor. Yanl\u0131\u015f RTSP aktar\u0131m protokol\u00fc?", + "stream_unauthorised": "Ak\u0131\u015fa ba\u011flanmaya \u00e7al\u0131\u015f\u0131rken yetkilendirme ba\u015far\u0131s\u0131z oldu", + "timeout": "URL y\u00fcklenirken zaman a\u015f\u0131m\u0131", + "unable_still_load": "Hareketsiz resim URL'sinden ge\u00e7erli resim y\u00fcklenemiyor (\u00f6r. ge\u00e7ersiz ana bilgisayar, URL veya kimlik do\u011frulama hatas\u0131). Daha fazla bilgi i\u00e7in g\u00fcnl\u00fc\u011f\u00fc inceleyin.", + "unknown": "Beklenmeyen hata" + }, + "step": { + "content_type": { + "data": { + "content_type": "\u0130\u00e7erik T\u00fcr\u00fc" + }, + "description": "Ak\u0131\u015f i\u00e7in i\u00e7erik t\u00fcr\u00fcn\u00fc belirtin." + }, + "init": { + "data": { + "authentication": "Kimlik Do\u011frulama", + "content_type": "\u0130\u00e7erik T\u00fcr\u00fc", + "framerate": "Kare H\u0131z\u0131 (Hz)", + "limit_refetch_to_url_change": "Yeniden getirmeyi url de\u011fi\u015fikli\u011fiyle s\u0131n\u0131rla", + "password": "Parola", + "rtsp_transport": "RTSP aktar\u0131m protokol\u00fc", + "still_image_url": "Hareketsiz G\u00f6r\u00fcnt\u00fc URL'si (\u00f6rne\u011fin http://...)", + "stream_source": "Ak\u0131\u015f Kayna\u011f\u0131 URL'si (\u00f6r. rtsp://...)", + "username": "Kullan\u0131c\u0131 Ad\u0131", + "verify_ssl": "SSL sertifikas\u0131n\u0131 do\u011frulay\u0131n" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic/translations/zh-Hant.json b/homeassistant/components/generic/translations/zh-Hant.json new file mode 100644 index 00000000000..1b72dcb0ce4 --- /dev/null +++ b/homeassistant/components/generic/translations/zh-Hant.json @@ -0,0 +1,88 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "error": { + "already_exists": "\u5df2\u7d93\u6709\u4f7f\u7528\u76f8\u540c URL \u7684\u651d\u5f71\u6a5f\u3002", + "invalid_still_image": "URL \u56de\u50b3\u4e26\u975e\u6709\u6548\u975c\u614b\u5f71\u50cf", + "no_still_image_or_stream_url": "\u5fc5\u9808\u81f3\u5c11\u6307\u5b9a\u975c\u614b\u5f71\u50cf\u6216\u4e32\u6d41 URL", + "stream_file_not_found": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u51fa\u73fe\u627e\u4e0d\u5230\u6a94\u6848\u932f\u8aa4\uff08\u662f\u5426\u5df2\u5b89\u88dd ffmpeg\uff1f\uff09", + "stream_http_not_found": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u51fa\u73fe HTTP 404 \u672a\u627e\u5230\u932f\u8aa4", + "stream_io_error": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u51fa\u73fe\u8f38\u5165/\u8f38\u51fa\u932f\u8aa4\u3002\u8f38\u5165\u932f\u8aa4\u7684 RTSP \u50b3\u8f38\u5354\u5b9a\uff1f", + "stream_no_route_to_host": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u627e\u4e0d\u5230\u4e3b\u6a5f", + "stream_no_video": "\u4e32\u6d41\u6c92\u6709\u5f71\u50cf", + "stream_not_permitted": "\u5617\u8a66\u4e32\u6d41\u9023\u7dda\u6642\u4e0d\u5141\u8a31\u64cd\u4f5c\u3002\u8f38\u5165\u932f\u8aa4\u7684 RTSP \u50b3\u8f38\u5354\u5b9a\uff1f", + "stream_unauthorised": "\u5617\u8a66\u4e32\u6d41\u9023\u7dda\u6642\u8a8d\u8b49\u5931\u6557", + "timeout": "\u8f09\u5165 URL \u903e\u6642\u6642\u9593", + "unable_still_load": "\u7121\u6cd5\u7531\u8a2d\u5b9a\u975c\u614b\u5f71\u50cf URL \u8f09\u5165\u6709\u6548\u5f71\u50cf\uff08\u4f8b\u5982\uff1a\u7121\u6548\u4e3b\u6a5f\u3001URL \u6216\u8a8d\u8b49\u5931\u6557\uff09\u3002\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8a0a\u606f\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "confirm": { + "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" + }, + "content_type": { + "data": { + "content_type": "\u5167\u5bb9\u985e\u578b" + }, + "description": "\u6307\u5b9a\u4e32\u6d41\u5167\u5bb9\u985e\u5225" + }, + "user": { + "data": { + "authentication": "\u9a57\u8b49", + "content_type": "\u5167\u5bb9\u985e\u578b", + "framerate": "\u5f71\u683c\u7387 (Hz)", + "limit_refetch_to_url_change": "\u9650\u5236 URL \u8b8a\u66f4\u66f4\u65b0", + "password": "\u5bc6\u78bc", + "rtsp_transport": "RTSP \u50b3\u8f38\u5354\u5b9a", + "still_image_url": "\u975c\u614b\u5f71\u50cf URL\uff08\u4f8b\u5982 http://...\uff09", + "stream_source": "\u4e32\u6d41\u4f86\u6e90 URL\uff08\u4f8b\u5982 rtsp://...\uff09", + "username": "\u4f7f\u7528\u8005\u540d\u7a31", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + }, + "description": "\u8f38\u5165\u651d\u5f71\u6a5f\u9023\u7dda\u8a2d\u5b9a\u3002" + } + } + }, + "options": { + "error": { + "already_exists": "\u5df2\u7d93\u6709\u4f7f\u7528\u76f8\u540c URL \u7684\u651d\u5f71\u6a5f\u3002", + "invalid_still_image": "URL \u56de\u50b3\u4e26\u975e\u6709\u6548\u975c\u614b\u5f71\u50cf", + "no_still_image_or_stream_url": "\u5fc5\u9808\u81f3\u5c11\u6307\u5b9a\u975c\u614b\u5f71\u50cf\u6216\u4e32\u6d41 URL", + "stream_file_not_found": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u51fa\u73fe\u627e\u4e0d\u5230\u6a94\u6848\u932f\u8aa4\uff08\u662f\u5426\u5df2\u5b89\u88dd ffmpeg\uff1f\uff09", + "stream_http_not_found": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u51fa\u73fe HTTP 404 \u672a\u627e\u5230\u932f\u8aa4", + "stream_io_error": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u51fa\u73fe\u8f38\u5165/\u8f38\u51fa\u932f\u8aa4\u3002\u8f38\u5165\u932f\u8aa4\u7684 RTSP \u50b3\u8f38\u5354\u5b9a\uff1f", + "stream_no_route_to_host": "\u5617\u8a66\u9023\u7dda\u4e32\u6d41\u6642\u627e\u4e0d\u5230\u4e3b\u6a5f", + "stream_no_video": "\u4e32\u6d41\u6c92\u6709\u5f71\u50cf", + "stream_not_permitted": "\u5617\u8a66\u4e32\u6d41\u9023\u7dda\u6642\u4e0d\u5141\u8a31\u64cd\u4f5c\u3002\u8f38\u5165\u932f\u8aa4\u7684 RTSP \u50b3\u8f38\u5354\u5b9a\uff1f", + "stream_unauthorised": "\u5617\u8a66\u4e32\u6d41\u9023\u7dda\u6642\u8a8d\u8b49\u5931\u6557", + "timeout": "\u8f09\u5165 URL \u903e\u6642\u6642\u9593", + "unable_still_load": "\u7121\u6cd5\u7531\u8a2d\u5b9a\u975c\u614b\u5f71\u50cf URL \u8f09\u5165\u6709\u6548\u5f71\u50cf\uff08\u4f8b\u5982\uff1a\u7121\u6548\u4e3b\u6a5f\u3001URL \u6216\u8a8d\u8b49\u5931\u6557\uff09\u3002\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8a0a\u606f\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "content_type": { + "data": { + "content_type": "\u5167\u5bb9\u985e\u578b" + }, + "description": "\u6307\u5b9a\u4e32\u6d41\u5167\u5bb9\u985e\u5225" + }, + "init": { + "data": { + "authentication": "\u9a57\u8b49", + "content_type": "\u5167\u5bb9\u985e\u578b", + "framerate": "\u5f71\u683c\u7387 (Hz)", + "limit_refetch_to_url_change": "\u9650\u5236 URL \u8b8a\u66f4\u66f4\u65b0", + "password": "\u5bc6\u78bc", + "rtsp_transport": "RTSP \u50b3\u8f38\u5354\u5b9a", + "still_image_url": "\u975c\u614b\u5f71\u50cf URL\uff08\u4f8b\u5982 http://...\uff09", + "stream_source": "\u4e32\u6d41\u4f86\u6e90 URL\uff08\u4f8b\u5982 rtsp://...\uff09", + "username": "\u4f7f\u7528\u8005\u540d\u7a31", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/generic_hygrostat/humidifier.py b/homeassistant/components/generic_hygrostat/humidifier.py index 05851bf339d..fa8b05b5ef8 100644 --- a/homeassistant/components/generic_hygrostat/humidifier.py +++ b/homeassistant/components/generic_hygrostat/humidifier.py @@ -8,12 +8,12 @@ from homeassistant.components.humidifier import ( PLATFORM_SCHEMA, HumidifierDeviceClass, HumidifierEntity, + HumidifierEntityFeature, ) from homeassistant.components.humidifier.const import ( ATTR_HUMIDITY, MODE_AWAY, MODE_NORMAL, - SUPPORT_MODES, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -57,7 +57,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_SAVED_HUMIDITY = "saved_humidity" -SUPPORT_FLAGS = 0 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(HYGROSTAT_SCHEMA.schema) @@ -148,9 +147,9 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): self._min_humidity = min_humidity self._max_humidity = max_humidity self._target_humidity = target_humidity - self._support_flags = SUPPORT_FLAGS + self._attr_supported_features = 0 if away_humidity: - self._support_flags = SUPPORT_FLAGS | SUPPORT_MODES + self._attr_supported_features |= HumidifierEntityFeature.MODES self._away_humidity = away_humidity self._away_fixed = away_fixed self._sensor_stale_duration = sensor_stale_duration @@ -434,11 +433,6 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): """If the toggleable device is currently active.""" return self.hass.states.is_state(self._switch_entity_id, STATE_ON) - @property - def supported_features(self): - """Return the list of supported features.""" - return self._support_flags - async def _async_device_turn_on(self): """Turn humidifier toggleable device on.""" data = {ATTR_ENTITY_ID: self._switch_entity_id} diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index b9fdcbbfd94..9e6df475d39 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -10,21 +10,15 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_PRESET_MODE, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_ACTIVITY, PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_NONE, PRESET_SLEEP, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -73,7 +67,7 @@ CONF_HOT_TOLERANCE = "hot_tolerance" CONF_KEEP_ALIVE = "keep_alive" CONF_INITIAL_HVAC_MODE = "initial_hvac_mode" CONF_PRECISION = "precision" -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE +CONF_TEMP_STEP = "target_temp_step" CONF_PRESETS = { p: f"{p}_temp" @@ -100,11 +94,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), vol.Optional(CONF_KEEP_ALIVE): cv.positive_time_period, vol.Optional(CONF_INITIAL_HVAC_MODE): vol.In( - [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] + [HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF] ), vol.Optional(CONF_PRECISION): vol.In( [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] ), + vol.Optional(CONF_TEMP_STEP): vol.In( + [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), vol.Optional(CONF_UNIQUE_ID): cv.string, } ).extend({vol.Optional(v): vol.Coerce(float) for (k, v) in CONF_PRESETS.items()}) @@ -136,6 +133,7 @@ async def async_setup_platform( key: config[value] for key, value in CONF_PRESETS.items() if value in config } precision = config.get(CONF_PRECISION) + target_temperature_step = config.get(CONF_TEMP_STEP) unit = hass.config.units.temperature_unit unique_id = config.get(CONF_UNIQUE_ID) @@ -156,6 +154,7 @@ async def async_setup_platform( initial_hvac_mode, presets, precision, + target_temperature_step, unit, unique_id, ) @@ -182,6 +181,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): initial_hvac_mode, presets, precision, + target_temperature_step, unit, unique_id, ): @@ -197,10 +197,11 @@ class GenericThermostat(ClimateEntity, RestoreEntity): self._hvac_mode = initial_hvac_mode self._saved_target_temp = target_temp or next(iter(presets.values()), None) self._temp_precision = precision + self._temp_target_temperature_step = target_temperature_step if self.ac_mode: - self._hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF] + self._hvac_list = [HVACMode.COOL, HVACMode.OFF] else: - self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF] + self._hvac_list = [HVACMode.HEAT, HVACMode.OFF] self._active = False self._cur_temp = None self._temp_lock = asyncio.Lock() @@ -210,9 +211,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity): self._target_temp = target_temp self._unit = unit self._unique_id = unique_id - self._support_flags = SUPPORT_FLAGS + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE if len(presets): - self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE self._attr_preset_modes = [PRESET_NONE] + list(presets.keys()) else: self._attr_preset_modes = [PRESET_NONE] @@ -297,7 +298,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): # Set default state to off if not self._hvac_mode: - self._hvac_mode = HVAC_MODE_OFF + self._hvac_mode = HVACMode.OFF @property def should_poll(self): @@ -324,8 +325,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity): @property def target_temperature_step(self): """Return the supported step of target temperature.""" - # Since this integration does not yet have a step size parameter - # we have to re-use the precision as the step size for now. + if self._temp_target_temperature_step is not None: + return self._temp_target_temperature_step + # if a target_temperature_step is not defined, fallback to equal the precision return self.precision @property @@ -349,13 +351,13 @@ class GenericThermostat(ClimateEntity, RestoreEntity): Need to be one of CURRENT_HVAC_*. """ - if self._hvac_mode == HVAC_MODE_OFF: - return CURRENT_HVAC_OFF + if self._hvac_mode == HVACMode.OFF: + return HVACAction.OFF if not self._is_device_active: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if self.ac_mode: - return CURRENT_HVAC_COOL - return CURRENT_HVAC_HEAT + return HVACAction.COOLING + return HVACAction.HEATING @property def target_temperature(self): @@ -369,14 +371,14 @@ class GenericThermostat(ClimateEntity, RestoreEntity): async def async_set_hvac_mode(self, hvac_mode): """Set hvac mode.""" - if hvac_mode == HVAC_MODE_HEAT: - self._hvac_mode = HVAC_MODE_HEAT + if hvac_mode == HVACMode.HEAT: + self._hvac_mode = HVACMode.HEAT await self._async_control_heating(force=True) - elif hvac_mode == HVAC_MODE_COOL: - self._hvac_mode = HVAC_MODE_COOL + elif hvac_mode == HVACMode.COOL: + self._hvac_mode = HVACMode.COOL await self._async_control_heating(force=True) - elif hvac_mode == HVAC_MODE_OFF: - self._hvac_mode = HVAC_MODE_OFF + elif hvac_mode == HVACMode.OFF: + self._hvac_mode = HVACMode.OFF if self._is_device_active: await self._async_heater_turn_off() else: @@ -422,8 +424,8 @@ class GenericThermostat(ClimateEntity, RestoreEntity): self.async_write_ha_state() async def _check_switch_initial_state(self): - """Prevent the device from keep running if HVAC_MODE_OFF.""" - if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active: + """Prevent the device from keep running if HVACMode.OFF.""" + if self._hvac_mode == HVACMode.OFF and self._is_device_active: _LOGGER.warning( "The climate mode is OFF, but the switch device is ON. Turning off device %s", self.heater_entity_id, @@ -467,7 +469,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): self._target_temp, ) - if not self._active or self._hvac_mode == HVAC_MODE_OFF: + if not self._active or self._hvac_mode == HVACMode.OFF: return # If the `force` argument is True, we @@ -478,7 +480,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): if self._is_device_active: current_state = STATE_ON else: - current_state = HVAC_MODE_OFF + current_state = HVACMode.OFF try: long_enough = condition.state( self.hass, @@ -524,11 +526,6 @@ class GenericThermostat(ClimateEntity, RestoreEntity): return self.hass.states.is_state(self.heater_entity_id, STATE_ON) - @property - def supported_features(self): - """Return the list of supported features.""" - return self._support_flags - async def _async_heater_turn_on(self): """Turn heater toggleable device on.""" data = {ATTR_ENTITY_ID: self.heater_entity_id} diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 83e358acc34..947dd325064 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -335,7 +335,6 @@ class GeniusHeatingZone(GeniusZone): _max_temp: float _min_temp: float - _supported_features: int @property def current_temperature(self) -> float | None: @@ -362,11 +361,6 @@ class GeniusHeatingZone(GeniusZone): """Return the unit of measurement.""" return TEMP_CELSIUS - @property - def supported_features(self) -> int: - """Return the bitmask of supported features.""" - return self._supported_features - async def async_set_temperature(self, **kwargs) -> None: """Set a new target temperature for this zone.""" await self._zone.set_override( diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py index aca6012e594..429d51c0035 100644 --- a/homeassistant/components/geniushub/climate.py +++ b/homeassistant/components/geniushub/climate.py @@ -3,15 +3,11 @@ from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_ACTIVITY, PRESET_BOOST, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -20,7 +16,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import DOMAIN, GeniusHeatingZone # GeniusHub Zones support: Off, Timer, Override/Boost, Footprint & Linked modes -HA_HVAC_TO_GH = {HVAC_MODE_OFF: "off", HVAC_MODE_HEAT: "timer"} +HA_HVAC_TO_GH = {HVACMode.OFF: "off", HVACMode.HEAT: "timer"} GH_HVAC_TO_HA = {v: k for k, v in HA_HVAC_TO_GH.items()} HA_PRESET_TO_GH = {PRESET_ACTIVITY: "footprint", PRESET_BOOST: "override"} @@ -53,13 +49,16 @@ async def async_setup_platform( class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): """Representation of a Genius Hub climate device.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, broker, zone) -> None: """Initialize the climate device.""" super().__init__(broker, zone) self._max_temp = 28.0 self._min_temp = 4.0 - self._supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @property def icon(self) -> str: @@ -69,7 +68,7 @@ class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - return GH_HVAC_TO_HA.get(self._zone.data["mode"], HVAC_MODE_HEAT) + return GH_HVAC_TO_HA.get(self._zone.data["mode"], HVACMode.HEAT) @property def hvac_modes(self) -> list[str]: @@ -81,10 +80,10 @@ class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): """Return the current running hvac operation if supported.""" if "_state" in self._zone.data: # only for v3 API if not self._zone.data["_state"].get("bIsActive"): - return CURRENT_HVAC_OFF + return HVACAction.OFF if self._zone.data["_state"].get("bOutRequestHeat"): - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return HVACAction.HEATING + return HVACAction.IDLE return None @property @@ -99,7 +98,7 @@ class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): return [PRESET_ACTIVITY, PRESET_BOOST] return [PRESET_BOOST] - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set a new hvac mode.""" await self._zone.set_mode(HA_HVAC_TO_GH.get(hvac_mode)) diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index a64aa1345e1..3a6b446b8db 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -2,9 +2,8 @@ from __future__ import annotations from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant @@ -57,13 +56,17 @@ async def async_setup_platform( class GeniusWaterHeater(GeniusHeatingZone, WaterHeaterEntity): """Representation of a Genius Hub water_heater device.""" + _attr_supported_features = ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + ) + def __init__(self, broker, zone) -> None: """Initialize the water_heater device.""" super().__init__(broker, zone) self._max_temp = 80.0 self._min_temp = 30.0 - self._supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @property def operation_list(self) -> list[str]: diff --git a/homeassistant/components/gios/diagnostics.py b/homeassistant/components/gios/diagnostics.py new file mode 100644 index 00000000000..b056473fbec --- /dev/null +++ b/homeassistant/components/gios/diagnostics.py @@ -0,0 +1,24 @@ +"""Diagnostics support for GIOS.""" +from __future__ import annotations + +from dataclasses import asdict + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import GiosDataUpdateCoordinator +from .const import DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict: + """Return diagnostics for a config entry.""" + coordinator: GiosDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + diagnostics_data = { + "config_entry": config_entry.as_dict(), + "coordinator_data": asdict(coordinator.data), + } + + return diagnostics_data diff --git a/homeassistant/components/gios/translations/hu.json b/homeassistant/components/gios/translations/hu.json index 9454aceb13d..1de37cee96a 100644 --- a/homeassistant/components/gios/translations/hu.json +++ b/homeassistant/components/gios/translations/hu.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "station_id": "A m\u00e9r\u0151\u00e1llom\u00e1s azonos\u00edt\u00f3ja" }, "description": "A GIO\u015a (lengyel k\u00f6rnyezetv\u00e9delmi f\u0151fel\u00fcgyel\u0151) leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha seg\u00edts\u00e9gre van sz\u00fcks\u00e9ged a konfigur\u00e1ci\u00f3val kapcsolatban, l\u00e1togass ide: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/gitter/manifest.json b/homeassistant/components/gitter/manifest.json index efd4ff3d28b..83b69e72ee8 100644 --- a/homeassistant/components/gitter/manifest.json +++ b/homeassistant/components/gitter/manifest.json @@ -3,7 +3,7 @@ "name": "Gitter", "documentation": "https://www.home-assistant.io/integrations/gitter", "requirements": ["gitterpy==0.1.7"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_polling", "loggers": ["gitterpy"] } diff --git a/homeassistant/components/glances/manifest.json b/homeassistant/components/glances/manifest.json index cce95957ff6..73f4d2f333f 100644 --- a/homeassistant/components/glances/manifest.json +++ b/homeassistant/components/glances/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/glances", "requirements": ["glances_api==0.3.4"], - "codeowners": ["@fabaff", "@engrbm87"], + "codeowners": ["@engrbm87"], "iot_class": "local_polling", "loggers": ["glances_api"] } diff --git a/homeassistant/components/glances/translations/hu.json b/homeassistant/components/glances/translations/hu.json index d93fa4bb66e..71649b51d34 100644 --- a/homeassistant/components/glances/translations/hu.json +++ b/homeassistant/components/glances/translations/hu.json @@ -11,7 +11,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "port": "Port", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", diff --git a/homeassistant/components/goalzero/translations/ca.json b/homeassistant/components/goalzero/translations/ca.json index 0f9979eb378..3e7d6e714d3 100644 --- a/homeassistant/components/goalzero/translations/ca.json +++ b/homeassistant/components/goalzero/translations/ca.json @@ -20,7 +20,7 @@ "host": "Amfitri\u00f3", "name": "Nom" }, - "description": "En primer lloc, has de descarregar-te l'aplicaci\u00f3 Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nSegueix les instruccions per connectar el Yeti al teu Wi-Fi. Es recomana que la reserva DHCP del router estigui configurada, si no ho est\u00e0, pot ser que el dispositiu no estigui disponible mentre Home Assistant no detecti la nova IP. Consulta el manual del router.", + "description": "Consulta la documentaci\u00f3 per assegurar-te que compleixes tots els requisits.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/de.json b/homeassistant/components/goalzero/translations/de.json index 5133488c247..a9f7e3fa887 100644 --- a/homeassistant/components/goalzero/translations/de.json +++ b/homeassistant/components/goalzero/translations/de.json @@ -20,7 +20,7 @@ "host": "Host", "name": "Name" }, - "description": "Zuerst musst du die Goal Zero App herunterladen: https://www.goalzero.com/product-features/yeti-app/ \n\nFolge den Anweisungen, um deinen Yeti mit deinem WLAN-Netzwerk zu verbinden. Eine DHCP-Reservierung auf deinem Router wird empfohlen. Wenn es nicht eingerichtet ist, ist das Ger\u00e4t m\u00f6glicherweise nicht verf\u00fcgbar, bis Home Assistant die neue IP-Adresse erkennt. Schlage dazu im Benutzerhandbuch deines Routers nach.", + "description": "Bitte lies die Dokumentation, um sicherzustellen, dass alle Anforderungen erf\u00fcllt sind.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/en.json b/homeassistant/components/goalzero/translations/en.json index 26a92757a4b..fd27892c794 100644 --- a/homeassistant/components/goalzero/translations/en.json +++ b/homeassistant/components/goalzero/translations/en.json @@ -20,7 +20,7 @@ "host": "Host", "name": "Name" }, - "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wi-fi network. DHCP reservation on your router is recommended. If not set up, the device may become unavailable until Home Assistant detects the new ip address. Refer to your router's user manual.", + "description": "Please refer to the documentation to make sure all requirements are met.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/et.json b/homeassistant/components/goalzero/translations/et.json index 5d0111aa946..e529f0a3a4e 100644 --- a/homeassistant/components/goalzero/translations/et.json +++ b/homeassistant/components/goalzero/translations/et.json @@ -20,7 +20,7 @@ "host": "", "name": "Nimi" }, - "description": "Alustuseks pead alla laadima rakenduse Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nYeti Wifi-v\u00f5rguga \u00fchendamiseks j\u00e4rgi juhiseid. DHCP peab olema ruuteri seadetes seadistatud nii, et hosti IP ei muutuks. Vaata ruuteri kasutusjuhendit.", + "description": "Vaata dokumentatsiooni, et veenduda, et k\u00f5ik n\u00f5uded on t\u00e4idetud.", "title": "" } } diff --git a/homeassistant/components/goalzero/translations/fr.json b/homeassistant/components/goalzero/translations/fr.json index b554ec1ea1d..430ad9927c4 100644 --- a/homeassistant/components/goalzero/translations/fr.json +++ b/homeassistant/components/goalzero/translations/fr.json @@ -20,7 +20,7 @@ "host": "H\u00f4te", "name": "Nom" }, - "description": "Vous devez tout d'abord t\u00e9l\u00e9charger l'application Goal Zero\u00a0: https://www.goalzero.com/product-features/yeti-app/\n\nSuivez les instructions pour connecter votre Yeti \u00e0 votre r\u00e9seau Wi-Fi. Il est recommand\u00e9 d'utiliser la r\u00e9servation DHCP sur votre routeur, sans quoi l'appareil risque de devenir indisponible le temps que Home Assistant d\u00e9tecte la nouvelle adresse IP. Reportez-vous au manuel d'utilisation de votre routeur.", + "description": "Veuillez consulter la documentation afin de vous assurer que toutes les conditions sont respect\u00e9es.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/hu.json b/homeassistant/components/goalzero/translations/hu.json index 62c0a1626f9..00196952e0c 100644 --- a/homeassistant/components/goalzero/translations/hu.json +++ b/homeassistant/components/goalzero/translations/hu.json @@ -18,9 +18,9 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, - "description": "El\u0151sz\u00f6r le kell t\u00f6ltenie a Goal Zero alkalmaz\u00e1st: https://www.goalzero.com/product-features/yeti-app/ \n\nK\u00f6vesse az utas\u00edt\u00e1sokat, hogy csatlakoztassa Yeti k\u00e9sz\u00fcl\u00e9k\u00e9t a Wi-Fi h\u00e1l\u00f3zathoz. DHCP foglal\u00e1s aj\u00e1nlott az routeren. Ha nincs be\u00e1ll\u00edtva, akkor az eszk\u00f6z el\u00e9rhetetlenn\u00e9 v\u00e1lhat, am\u00edg a Home Assistant \u00e9szleli az \u00faj IP-c\u00edmet. Olvassa el az router felhaszn\u00e1l\u00f3i k\u00e9zik\u00f6nyv\u00e9t.", + "description": "K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t, hogy megbizonyosodjon arr\u00f3l, hogy minden k\u00f6vetelm\u00e9ny teljes\u00fcl.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/id.json b/homeassistant/components/goalzero/translations/id.json index d5897a2d944..d524b13bb0c 100644 --- a/homeassistant/components/goalzero/translations/id.json +++ b/homeassistant/components/goalzero/translations/id.json @@ -20,7 +20,7 @@ "host": "Host", "name": "Nama" }, - "description": "Pertama, Anda perlu mengunduh aplikasi Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nIkuti petunjuk untuk menghubungkan Yeti Anda ke jaringan Wi-Fi Anda. Kemudian dapatkan IP host dari router Anda. DHCP harus disetel di pengaturan router Anda untuk perangkat host agar IP host tidak berubah. Lihat manual pengguna router Anda.", + "description": "Rujuk ke dokumentasi untuk memastikan semua persyaratan terpenuhi.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/it.json b/homeassistant/components/goalzero/translations/it.json index ad5042cc602..dbd71c82f4a 100644 --- a/homeassistant/components/goalzero/translations/it.json +++ b/homeassistant/components/goalzero/translations/it.json @@ -20,7 +20,7 @@ "host": "Host", "name": "Nome" }, - "description": "Innanzitutto, devi scaricare l'app Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nSegui le istruzioni per connettere il tuo Yeti alla tua rete Wi-Fi. Si consiglia la prenotazione DHCP sul router. Se non configurato, il dispositivo potrebbe non essere disponibile fino a quando Home Assistant non rileva il nuovo indirizzo IP. Fare riferimento al manuale utente del router.", + "description": "Fai riferimento alla documentazione per assicurarti che tutti i requisiti siano soddisfatti.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index d73c4d648ed..680ff1a10cc 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -20,7 +20,7 @@ "host": "Host", "name": "Naam" }, - "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om Yeti te verbinden met uw wifi-netwerk. DHCP-reservering op uw router wordt aanbevolen. Als het niet is ingesteld, is het apparaat mogelijk niet meer beschikbaar totdat Home Assistant het nieuwe IP-adres detecteert. Raadpleeg de gebruikershandleiding van uw router.", + "description": "Raadpleeg de documentatie om er zeker van te zijn dat aan alle vereisten wordt voldaan.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/no.json b/homeassistant/components/goalzero/translations/no.json index 1bc2d6feae1..6bb00952fab 100644 --- a/homeassistant/components/goalzero/translations/no.json +++ b/homeassistant/components/goalzero/translations/no.json @@ -20,7 +20,7 @@ "host": "Vert", "name": "Navn" }, - "description": "F\u00f8rst m\u00e5 du laste ned Goal Zero-appen: https://www.goalzero.com/product-features/yeti-app/\n\nF\u00f8lg instruksjonene for \u00e5 koble Yeti til Wi-Fi-nettverket ditt. DHCP-reservasjon p\u00e5 ruteren anbefales. Hvis den ikke er konfigurert, kan enheten bli utilgjengelig til Home Assistant oppdager den nye IP-adressen. Se brukerh\u00e5ndboken for ruteren.", + "description": "Se dokumentasjonen for \u00e5 sikre at alle krav er oppfylt.", "title": "" } } diff --git a/homeassistant/components/goalzero/translations/pl.json b/homeassistant/components/goalzero/translations/pl.json index 3aba221bc4a..ee8f2022a0d 100644 --- a/homeassistant/components/goalzero/translations/pl.json +++ b/homeassistant/components/goalzero/translations/pl.json @@ -20,7 +20,7 @@ "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, - "description": "Najpierw musisz pobra\u0107 aplikacj\u0119 Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nPost\u0119puj zgodnie z instrukcjami, aby pod\u0142\u0105czy\u0107 Yeti do sieci Wi-Fi. Zaleca si\u0119 rezerwacj\u0119 DHCP w ustawieniach routera. Je\u015bli tego nie ustawisz, urz\u0105dzenie mo\u017ce sta\u0107 si\u0119 niedost\u0119pne, do czasu a\u017c Home Assistant wykryje nowy adres IP. Post\u0119puj wg instrukcji obs\u0142ugi routera.", + "description": "Zapoznaj si\u0119 z dokumentacj\u0105, aby upewni\u0107 si\u0119, \u017ce wszystkie wymagania s\u0105 spe\u0142nione.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/pt-BR.json b/homeassistant/components/goalzero/translations/pt-BR.json index 7ecea696702..fa67129b08a 100644 --- a/homeassistant/components/goalzero/translations/pt-BR.json +++ b/homeassistant/components/goalzero/translations/pt-BR.json @@ -20,7 +20,7 @@ "host": "Nome do host", "name": "Nome" }, - "description": "Primeiro, voc\u00ea precisa baixar o aplicativo Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n Siga as instru\u00e7\u00f5es para conectar seu Yeti \u00e0 sua rede Wi-fi. A reserva de DHCP em seu roteador \u00e9 recomendada. Se n\u00e3o estiver configurado, o dispositivo pode ficar indispon\u00edvel at\u00e9 que o Home Assistant detecte o novo endere\u00e7o IP. Consulte o manual do usu\u00e1rio do seu roteador.", + "description": "Consulte a documenta\u00e7\u00e3o para garantir que todos os requisitos sejam atendidos.", "title": "Gol Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/ru.json b/homeassistant/components/goalzero/translations/ru.json index 52d8bbcbdc7..ca114f6d92e 100644 --- a/homeassistant/components/goalzero/translations/ru.json +++ b/homeassistant/components/goalzero/translations/ru.json @@ -20,7 +20,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Goal Zero: https://www.goalzero.com/product-features/yeti-app/.\n\n\u0421\u043b\u0435\u0434\u0443\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c \u043f\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044e Yeti \u043a \u0441\u0435\u0442\u0438 WiFi. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0442\u0430\u043a\u0438\u043c\u0438, \u0447\u0442\u043e\u0431\u044b IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u043b\u0441\u044f \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440, \u043f\u043e\u043a\u0430 Home Assistant \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442 \u043d\u043e\u0432\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0412\u0430\u0448\u0435\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430.", + "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0432\u0441\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0431\u043b\u044e\u0434\u0435\u043d\u044b.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/tr.json b/homeassistant/components/goalzero/translations/tr.json index a2c5b4f8986..9be07def514 100644 --- a/homeassistant/components/goalzero/translations/tr.json +++ b/homeassistant/components/goalzero/translations/tr.json @@ -20,7 +20,7 @@ "host": "Sunucu", "name": "Ad" }, - "description": "\u00d6ncelikle Goal Zero uygulamas\u0131n\u0131 indirmeniz gerekiyor: https://www.goalzero.com/product-features/yeti-app/ \n\n Yeti'nizi Wi-fi a\u011f\u0131n\u0131za ba\u011flamak i\u00e7in talimatlar\u0131 izleyin. Y\u00f6nlendiricinizde DHCP rezervasyonu yap\u0131lmas\u0131 \u00f6nerilir. Kurulmazsa, Home Assistant yeni ip adresini alg\u0131layana kadar cihaz kullan\u0131lamayabilir. Y\u00f6nlendiricinizin kullan\u0131m k\u0131lavuzuna bak\u0131n.", + "description": "T\u00fcm gereksinimlerin kar\u015f\u0131land\u0131\u011f\u0131ndan emin olmak i\u00e7in l\u00fctfen belgelere bak\u0131n.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/zh-Hant.json b/homeassistant/components/goalzero/translations/zh-Hant.json index 13c49f8d2ac..4f5b01fb9d4 100644 --- a/homeassistant/components/goalzero/translations/zh-Hant.json +++ b/homeassistant/components/goalzero/translations/zh-Hant.json @@ -20,7 +20,7 @@ "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u9996\u5148\u5fc5\u9808\u5148\u4e0b\u8f09 Goal Zero app\uff1ahttps://www.goalzero.com/product-features/yeti-app/\n\n\u8ddf\u96a8\u6307\u793a\u5c07 Yeti \u9023\u7dda\u81f3\u7121\u7dda\u7db2\u8def\u3002\u5efa\u8b70\u65bc\u8def\u7531\u5668\u7684 DHCP \u8a2d\u5b9a\u4e2d\u4fdd\u7559\u56fa\u5b9a IP\uff0c\u5047\u5982\u672a\u8a2d\u5b9a\u3001\u88dd\u7f6e\u53ef\u80fd\u6703\u5728 Home Assistant \u5075\u6e2c\u5230\u65b0 IP \u4e4b\u524d\u8b8a\u6210\u7121\u6cd5\u4f7f\u7528\u3002\u8acb\u53c3\u95b1\u8def\u7531\u5668\u624b\u518a\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a\u3002", + "description": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u6240\u6709\u5fc5\u8981\u9700\u6c42\u3002", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 69eba3390d8..af3bd1c7530 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -9,10 +9,9 @@ from ismartgate.common import ( ) from homeassistant.components.cover import ( - SUPPORT_CLOSE, - SUPPORT_OPEN, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -45,6 +44,8 @@ async def async_setup_entry( class DeviceCover(GoGoGate2Entity, CoverEntity): """Cover entity for gogogate2.""" + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + def __init__( self, config_entry: ConfigEntry, @@ -54,7 +55,6 @@ class DeviceCover(GoGoGate2Entity, CoverEntity): """Initialize the object.""" unique_id = cover_unique_id(config_entry, door) super().__init__(config_entry, data_update_coordinator, door, unique_id) - self._attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE self._attr_device_class = ( CoverDeviceClass.GATE if self.door.gate else CoverDeviceClass.GARAGE ) diff --git a/homeassistant/components/goodwe/sensor.py b/homeassistant/components/goodwe/sensor.py index d3393221c3a..96a8ef49af3 100644 --- a/homeassistant/components/goodwe/sensor.py +++ b/homeassistant/components/goodwe/sensor.py @@ -8,19 +8,13 @@ from typing import Any from goodwe import Inverter, Sensor, SensorKind from homeassistant.components.sensor import ( - STATE_CLASS_MEASUREMENT, - STATE_CLASS_TOTAL_INCREASING, + SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - DEVICE_CLASS_BATTERY, - DEVICE_CLASS_CURRENT, - DEVICE_CLASS_ENERGY, - DEVICE_CLASS_POWER, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_VOLTAGE, ELECTRIC_CURRENT_AMPERE, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, @@ -74,50 +68,50 @@ class GoodweSensorEntityDescription(SensorEntityDescription): _DESCRIPTIONS = { "A": GoodweSensorEntityDescription( key="A", - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.CURRENT, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, ), "V": GoodweSensorEntityDescription( key="V", - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, ), "W": GoodweSensorEntityDescription( key="W", - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=POWER_WATT, ), "kWh": GoodweSensorEntityDescription( key="kWh", - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value=lambda sensor, prev, val: prev if "total" in sensor and not val else val, ), "C": GoodweSensorEntityDescription( key="C", - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=TEMP_CELSIUS, ), "Hz": GoodweSensorEntityDescription( key="Hz", - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=FREQUENCY_HERTZ, ), "%": GoodweSensorEntityDescription( key="%", - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, ), } DIAG_SENSOR = GoodweSensorEntityDescription( key="_", - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ) @@ -166,7 +160,7 @@ class InverterSensor(CoordinatorEntity, SensorEntity): self._attr_icon = _ICONS.get(sensor.kind) # Set the inverter SoC as main device battery sensor if sensor.id_ == BATTERY_SOC: - self._attr_device_class = DEVICE_CLASS_BATTERY + self._attr_device_class = SensorDeviceClass.BATTERY self._sensor = sensor self._previous_value = None diff --git a/homeassistant/components/goodwe/translations/hu.json b/homeassistant/components/goodwe/translations/hu.json index 6f3fa5bbff3..e6086eab7aa 100644 --- a/homeassistant/components/goodwe/translations/hu.json +++ b/homeassistant/components/goodwe/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van" + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve" }, "error": { "connection_error": "Sikertelen csatlakoz\u00e1s" diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 80c6ce2b811..98eadead101 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -8,7 +8,9 @@ import logging from typing import Any import aiohttp -from httplib2.error import ServerNotFoundError +from gcal_sync.api import GoogleCalendarService +from gcal_sync.exceptions import ApiException +from gcal_sync.model import DateOrDatetime, Event from oauth2client.file import Storage import voluptuous as vol from voluptuous.error import Error as VoluptuousError @@ -31,13 +33,14 @@ from homeassistant.exceptions import ( HomeAssistantError, ) from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.typing import ConfigType from . import config_flow -from .api import DeviceAuth, GoogleCalendarService +from .api import ApiAuthImpl, DeviceAuth from .const import ( CONF_CALENDAR_ACCESS, DATA_CONFIG, @@ -212,7 +215,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryAuthFailed( "Required scopes are not available, reauth required" ) - calendar_service = GoogleCalendarService(hass, session) + calendar_service = GoogleCalendarService( + ApiAuthImpl(async_get_clientsession(hass), session) + ) hass.data[DOMAIN][DATA_SERVICE] = calendar_service await async_setup_services(hass, hass.data[DOMAIN][DATA_CONFIG], calendar_service) @@ -263,11 +268,12 @@ async def async_setup_services( async def _scan_for_calendars(call: ServiceCall) -> None: """Scan for new calendars.""" try: - calendars = await calendar_service.async_list_calendars() - except ServerNotFoundError as err: + result = await calendar_service.async_list_calendars() + except ApiException as err: raise HomeAssistantError(str(err)) from err tasks = [] - for calendar in calendars: + for calendar_item in result.items: + calendar = calendar_item.dict(exclude_unset=True) calendar[CONF_TRACK] = config[CONF_TRACK_NEW] tasks.append( hass.services.async_call(DOMAIN, SERVICE_FOUND_CALENDARS, calendar) @@ -278,8 +284,8 @@ async def async_setup_services( async def _add_event(call: ServiceCall) -> None: """Add a new event to calendar.""" - start = {} - end = {} + start: DateOrDatetime | None = None + end: DateOrDatetime | None = None if EVENT_IN in call.data: if EVENT_IN_DAYS in call.data[EVENT_IN]: @@ -288,8 +294,8 @@ async def async_setup_services( start_in = now + timedelta(days=call.data[EVENT_IN][EVENT_IN_DAYS]) end_in = start_in + timedelta(days=1) - start = {"date": start_in.strftime("%Y-%m-%d")} - end = {"date": end_in.strftime("%Y-%m-%d")} + start = DateOrDatetime(date=start_in) + end = DateOrDatetime(date=end_in) elif EVENT_IN_WEEKS in call.data[EVENT_IN]: now = datetime.now() @@ -297,29 +303,34 @@ async def async_setup_services( start_in = now + timedelta(weeks=call.data[EVENT_IN][EVENT_IN_WEEKS]) end_in = start_in + timedelta(days=1) - start = {"date": start_in.strftime("%Y-%m-%d")} - end = {"date": end_in.strftime("%Y-%m-%d")} + start = DateOrDatetime(date=start_in) + end = DateOrDatetime(date=end_in) elif EVENT_START_DATE in call.data: - start = {"date": str(call.data[EVENT_START_DATE])} - end = {"date": str(call.data[EVENT_END_DATE])} + start = DateOrDatetime(date=call.data[EVENT_START_DATE]) + end = DateOrDatetime(date=call.data[EVENT_END_DATE]) elif EVENT_START_DATETIME in call.data: - start_dt = str( - call.data[EVENT_START_DATETIME].strftime("%Y-%m-%dT%H:%M:%S") + start_dt = call.data[EVENT_START_DATETIME] + end_dt = call.data[EVENT_END_DATETIME] + start = DateOrDatetime( + date_time=start_dt, timezone=str(hass.config.time_zone) + ) + end = DateOrDatetime(date_time=end_dt, timezone=str(hass.config.time_zone)) + + if start is None or end is None: + raise ValueError( + "Missing required fields to set start or end date/datetime" ) - end_dt = str(call.data[EVENT_END_DATETIME].strftime("%Y-%m-%dT%H:%M:%S")) - start = {"dateTime": start_dt, "timeZone": str(hass.config.time_zone)} - end = {"dateTime": end_dt, "timeZone": str(hass.config.time_zone)} await calendar_service.async_create_event( call.data[EVENT_CALENDAR_ID], - { - "summary": call.data[EVENT_SUMMARY], - "description": call.data[EVENT_DESCRIPTION], - "start": start, - "end": end, - }, + Event( + summary=call.data[EVENT_SUMMARY], + description=call.data[EVENT_DESCRIPTION], + start=start, + end=end, + ), ) # Only expose the add event service if we have the correct permissions diff --git a/homeassistant/components/google/api.py b/homeassistant/components/google/api.py index 80c66d7af0c..5de563393e1 100644 --- a/homeassistant/components/google/api.py +++ b/homeassistant/components/google/api.py @@ -8,13 +8,13 @@ import logging import time from typing import Any -from googleapiclient import discovery as google_discovery +import aiohttp +from gcal_sync.auth import AbstractAuth import oauth2client from oauth2client.client import ( Credentials, DeviceFlowInfo, FlowExchangeError, - OAuth2Credentials, OAuth2DeviceCodeError, OAuth2WebServerFlow, ) @@ -150,95 +150,19 @@ async def async_create_device_flow(hass: HomeAssistant) -> DeviceFlow: return DeviceFlow(hass, oauth_flow, device_flow_info) -def _async_google_creds(hass: HomeAssistant, token: dict[str, Any]) -> Credentials: - """Convert a Home Assistant token to a Google API Credentials object.""" - conf = hass.data[DOMAIN][DATA_CONFIG] - return OAuth2Credentials( - access_token=token["access_token"], - client_id=conf[CONF_CLIENT_ID], - client_secret=conf[CONF_CLIENT_SECRET], - refresh_token=token["refresh_token"], - token_expiry=datetime.datetime.fromtimestamp(token["expires_at"]), - token_uri=oauth2client.GOOGLE_TOKEN_URI, - scopes=[conf[CONF_CALENDAR_ACCESS].scope], - user_agent=None, - ) - - -def _api_time_format(date_time: datetime.datetime | None) -> str | None: - """Convert a datetime to the api string format.""" - return date_time.isoformat("T") if date_time else None - - -class GoogleCalendarService: - """Calendar service interface to Google.""" +class ApiAuthImpl(AbstractAuth): + """Authentication implementation for google calendar api library.""" def __init__( - self, hass: HomeAssistant, session: config_entry_oauth2_flow.OAuth2Session + self, + websession: aiohttp.ClientSession, + session: config_entry_oauth2_flow.OAuth2Session, ) -> None: - """Init the Google Calendar service.""" - self._hass = hass + """Init the Google Calendar client library auth implementation.""" + super().__init__(websession) self._session = session - async def _async_get_service(self) -> google_discovery.Resource: - """Get the calendar service with valid credetnails.""" + async def async_get_access_token(self) -> str: + """Return a valid access token.""" await self._session.async_ensure_token_valid() - creds = _async_google_creds(self._hass, self._session.token) - - def _build() -> google_discovery.Resource: - return google_discovery.build( - "calendar", "v3", credentials=creds, cache_discovery=False - ) - - return await self._hass.async_add_executor_job(_build) - - async def async_list_calendars( - self, - ) -> list[dict[str, Any]]: - """Return the list of calendars the user has added to their list.""" - service = await self._async_get_service() - - def _list_calendars() -> list[dict[str, Any]]: - cal_list = service.calendarList() - return cal_list.list().execute()["items"] - - return await self._hass.async_add_executor_job(_list_calendars) - - async def async_create_event( - self, calendar_id: str, event: dict[str, Any] - ) -> dict[str, Any]: - """Return the list of calendars the user has added to their list.""" - service = await self._async_get_service() - - def _create_event() -> dict[str, Any]: - events = service.events() - return events.insert(calendarId=calendar_id, body=event).execute() - - return await self._hass.async_add_executor_job(_create_event) - - async def async_list_events( - self, - calendar_id: str, - start_time: datetime.datetime | None = None, - end_time: datetime.datetime | None = None, - search: str | None = None, - page_token: str | None = None, - ) -> tuple[list[dict[str, Any]], str | None]: - """Return the list of events.""" - service = await self._async_get_service() - - def _list_events() -> tuple[list[dict[str, Any]], str | None]: - events = service.events() - result = events.list( - calendarId=calendar_id, - timeMin=_api_time_format(start_time if start_time else dt.now()), - timeMax=_api_time_format(end_time), - q=search, - maxResults=EVENT_PAGE_SIZE, - pageToken=page_token, - singleEvents=True, # Flattens recurring events - orderBy="startTime", - ).execute() - return (result["items"], result.get("nextPageToken")) - - return await self._hass.async_add_executor_job(_list_events) + return self._session.token["access_token"] diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index f9c05d3c846..b0d13c0c0c6 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -6,13 +6,15 @@ from datetime import datetime, timedelta import logging from typing import Any -from httplib2 import ServerNotFoundError +from gcal_sync.api import GoogleCalendarService, ListEventsRequest +from gcal_sync.exceptions import ApiException +from gcal_sync.model import Event from homeassistant.components.calendar import ( ENTITY_ID_FORMAT, - CalendarEventDevice, + CalendarEntity, + CalendarEvent, extract_offset, - get_date, is_offset_reached, ) from homeassistant.config_entries import ConfigEntry @@ -34,7 +36,6 @@ from . import ( DOMAIN, SERVICE_SCAN_CALENDARS, ) -from .api import GoogleCalendarService from .const import DISCOVER_CALENDAR _LOGGER = logging.getLogger(__name__) @@ -94,7 +95,7 @@ def _async_setup_entities( entity_id = generate_entity_id( ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass ) - entity = GoogleCalendarEventDevice( + entity = GoogleCalendarEntity( calendar_service, disc_info[CONF_CAL_ID], data, entity_id ) entities.append(entity) @@ -102,7 +103,7 @@ def _async_setup_entities( async_add_entities(entities, True) -class GoogleCalendarEventDevice(CalendarEventDevice): +class GoogleCalendarEntity(CalendarEntity): """A calendar event device.""" def __init__( @@ -117,7 +118,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice): self._calendar_id = calendar_id self._search: str | None = data.get(CONF_SEARCH) self._ignore_availability: bool = data.get(CONF_IGNORE_AVAILABILITY, False) - self._event: dict[str, Any] | None = None + self._event: CalendarEvent | None = None self._name: str = data[CONF_NAME] self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) self._offset_value: timedelta | None = None @@ -132,11 +133,13 @@ class GoogleCalendarEventDevice(CalendarEventDevice): def offset_reached(self) -> bool: """Return whether or not the event offset was reached.""" if self._event and self._offset_value: - return is_offset_reached(get_date(self._event["start"]), self._offset_value) + return is_offset_reached( + self._event.start_datetime_local, self._offset_value + ) return False @property - def event(self) -> dict[str, Any] | None: + def event(self) -> CalendarEvent | None: """Return the next upcoming event.""" return self._event @@ -145,53 +148,63 @@ class GoogleCalendarEventDevice(CalendarEventDevice): """Return the name of the entity.""" return self._name - def _event_filter(self, event: dict[str, Any]) -> bool: + def _event_filter(self, event: Event) -> bool: """Return True if the event is visible.""" if self._ignore_availability: return True - return event.get(TRANSPARENCY, OPAQUE) == OPAQUE + return event.transparency == OPAQUE async def async_get_events( self, hass: HomeAssistant, start_date: datetime, end_date: datetime - ) -> list[dict[str, Any]]: + ) -> list[CalendarEvent]: """Get all events in a specific time frame.""" - event_list: list[dict[str, Any]] = [] - page_token: str | None = None - while True: - try: - items, page_token = await self._calendar_service.async_list_events( - self._calendar_id, - start_time=start_date, - end_time=end_date, - search=self._search, - page_token=page_token, - ) - except ServerNotFoundError as err: - _LOGGER.error("Unable to connect to Google: %s", err) - return [] - event_list.extend(filter(self._event_filter, items)) - if not page_token: - break - return event_list + request = ListEventsRequest( + calendar_id=self._calendar_id, + start_time=start_date, + end_time=end_date, + search=self._search, + ) + result_items = [] + try: + result = await self._calendar_service.async_list_events(request) + async for result_page in result: + result_items.extend(result_page.items) + except ApiException as err: + _LOGGER.error("Unable to connect to Google: %s", err) + return [] + return [ + _get_calendar_event(event) + for event in filter(self._event_filter, result_items) + ] @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self) -> None: """Get the latest data.""" + request = ListEventsRequest(calendar_id=self._calendar_id, search=self._search) try: - items, _ = await self._calendar_service.async_list_events( - self._calendar_id, search=self._search - ) - except ServerNotFoundError as err: + result = await self._calendar_service.async_list_events(request) + except ApiException as err: _LOGGER.error("Unable to connect to Google: %s", err) return # Pick the first visible event and apply offset calculations. - valid_items = filter(self._event_filter, items) - self._event = copy.deepcopy(next(valid_items, None)) - if self._event: - (summary, offset) = extract_offset( - self._event.get("summary", ""), self._offset - ) - self._event["summary"] = summary + valid_items = filter(self._event_filter, result.items) + event = copy.deepcopy(next(valid_items, None)) + if event: + (event.summary, offset) = extract_offset(event.summary, self._offset) + self._event = _get_calendar_event(event) self._offset_value = offset + else: + self._event = None + + +def _get_calendar_event(event: Event) -> CalendarEvent: + """Return a CalendarEvent from an API event.""" + return CalendarEvent( + summary=event.summary, + start=event.start.value, + end=event.end.value, + description=event.description, + location=event.location, + ) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 589ecb25b21..2cf852fc6af 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -2,13 +2,9 @@ "domain": "google", "name": "Google Calendars", "config_flow": true, - "dependencies": ["http"], + "dependencies": ["auth"], "documentation": "https://www.home-assistant.io/integrations/calendar.google/", - "requirements": [ - "google-api-python-client==2.38.0", - "httplib2==0.20.4", - "oauth2client==4.1.3" - ], + "requirements": ["gcal-sync==0.7.1", "oauth2client==4.1.3"], "codeowners": ["@allenporter"], "iot_class": "cloud_polling", "loggers": ["googleapiclient"] diff --git a/homeassistant/components/google/translations/bg.json b/homeassistant/components/google/translations/bg.json new file mode 100644 index 00000000000..38b08fc3616 --- /dev/null +++ b/homeassistant/components/google/translations/bg.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", + "missing_configuration": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044a\u0442 \u043d\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d. \u041c\u043e\u043b\u044f, \u0441\u043b\u0435\u0434\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" + }, + "create_entry": { + "default": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0435\u043d\u043e" + }, + "step": { + "auth": { + "title": "\u0421\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u0430\u043a\u0430\u0443\u043d\u0442 \u0432 Google" + }, + "reauth_confirm": { + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/ca.json b/homeassistant/components/google/translations/ca.json new file mode 100644 index 00000000000..829ce1413d5 --- /dev/null +++ b/homeassistant/components/google/translations/ca.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "code_expired": "El codi d'autenticaci\u00f3 ha caducat o la configuraci\u00f3 de credencials no \u00e9s v\u00e0lida. Torna-ho a provar.", + "invalid_access_token": "Token d'acc\u00e9s inv\u00e0lid", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3.", + "oauth_error": "S'han rebut dades token inv\u00e0lides.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "progress": { + "exchange": "Per enlla\u00e7ar un compte de Google, v\u00e9s a [{url}]({url}) i introdueix el codi: \n\n{user_code}" + }, + "step": { + "auth": { + "title": "Vinculaci\u00f3 amb compte de Google" + }, + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + }, + "reauth_confirm": { + "description": "La integraci\u00f3 Google Calendar ha de tornar a autenticar-se amb el teu compte", + "title": "Reautenticaci\u00f3 de la integraci\u00f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/cs.json b/homeassistant/components/google/translations/cs.json new file mode 100644 index 00000000000..0c11a65f69b --- /dev/null +++ b/homeassistant/components/google/translations/cs.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "invalid_access_token": "Neplatn\u00fd p\u0159\u00edstupov\u00fd token", + "missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace.", + "oauth_error": "P\u0159ijata neplatn\u00e1 data tokenu.", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, + "create_entry": { + "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno" + }, + "step": { + "pick_implementation": { + "title": "Vyberte metodu ov\u011b\u0159en\u00ed" + }, + "reauth_confirm": { + "title": "Znovu ov\u011b\u0159it integraci" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/de.json b/homeassistant/components/google/translations/de.json new file mode 100644 index 00000000000..1b15c465497 --- /dev/null +++ b/homeassistant/components/google/translations/de.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "code_expired": "Der Authentifizierungscode ist abgelaufen oder die Anmeldedaten sind ung\u00fcltig, bitte versuche es erneut.", + "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "oauth_error": "Ung\u00fcltige Token-Daten empfangen.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "progress": { + "exchange": "Um dein Google-Konto zu verkn\u00fcpfen, besuche [{url}]({url}) und gib folgenden Code ein:\n\n{user_code}" + }, + "step": { + "auth": { + "title": "Google-Konto verkn\u00fcpfen" + }, + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + }, + "reauth_confirm": { + "description": "Die Google Kalender-Integration muss dein Konto erneut authentifizieren", + "title": "Integration erneut authentifizieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/el.json b/homeassistant/components/google/translations/el.json new file mode 100644 index 00000000000..11c78f96a93 --- /dev/null +++ b/homeassistant/components/google/translations/el.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "code_expired": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ad\u03bb\u03b7\u03be\u03b5 \u03ae \u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03b7\u03c1\u03af\u03c9\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ac\u03ba\u03c5\u03c1\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "invalid_access_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "oauth_error": "\u039b\u03ae\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "progress": { + "exchange": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Google, \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf [{url}]({url}) \u03ba\u03b1\u03b9 \u03c0\u03bb\u03b7\u03ba\u03c4\u03c1\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc:\n\n{user_code}" + }, + "step": { + "auth": { + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Google" + }, + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Nest \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03ad\u03b3\u03be\u03b5\u03b9 \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03c4\u03b7\u03bd \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/es.json b/homeassistant/components/google/translations/es.json new file mode 100644 index 00000000000..8072ac95d4b --- /dev/null +++ b/homeassistant/components/google/translations/es.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "missing_configuration": "El componente no est\u00e1 configurado. Por favor, sigue la documentaci\u00f3n." + }, + "step": { + "auth": { + "title": "Vincular cuenta de Google" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/et.json b/homeassistant/components/google/translations/et.json new file mode 100644 index 00000000000..27dfa3f5290 --- /dev/null +++ b/homeassistant/components/google/translations/et.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", + "code_expired": "Tuvastuskood on aegunud v\u00f5i mandaadi seadistus on vale, proovi uuesti.", + "invalid_access_token": "Vigane juurdep\u00e4\u00e4sut\u00f5end", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni.", + "oauth_error": "Saadi sobimatud loaandmed.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "progress": { + "exchange": "Google'i konto linkimiseks k\u00fclasta aadressi [ {url} ]( {url} ) ja sisesta kood: \n\n {user_code}" + }, + "step": { + "auth": { + "title": "Google'i konto linkimine" + }, + "pick_implementation": { + "title": "Vali tuvastusmeetod" + }, + "reauth_confirm": { + "description": "Google'i kalendri sidumine peab konto uuesti tuvastama", + "title": "Taastuvasta sidumine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/fr.json b/homeassistant/components/google/translations/fr.json new file mode 100644 index 00000000000..1224ba76c9b --- /dev/null +++ b/homeassistant/components/google/translations/fr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "code_expired": "Le code d'authentification a expir\u00e9 ou la configuration des informations d'identification n'est pas valide, veuillez r\u00e9essayer.", + "invalid_access_token": "Jeton d'acc\u00e8s non valide", + "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", + "oauth_error": "Des donn\u00e9es de jeton non valides ont \u00e9t\u00e9 re\u00e7ues.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "create_entry": { + "default": "Authentification r\u00e9ussie" + }, + "progress": { + "exchange": "Afin d'associer votre compte Google, rendez-vous sur [{url}]({url}) et saisissez le code suivant\u00a0:\n\n{user_code}" + }, + "step": { + "auth": { + "title": "Associer un compte Google" + }, + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + }, + "reauth_confirm": { + "description": "L'int\u00e9gration Google Agenda doit r\u00e9-authentifier votre compte", + "title": "R\u00e9-authentifier l'int\u00e9gration" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/he.json b/homeassistant/components/google/translations/he.json new file mode 100644 index 00000000000..df5ec28163e --- /dev/null +++ b/homeassistant/components/google/translations/he.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea", + "invalid_access_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d2\u05d9\u05e9\u05d4 \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "missing_configuration": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05e8\u05db\u05d9\u05d1 \u05dc\u05d0 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e0\u05d0 \u05e2\u05e7\u05d5\u05d1 \u05d0\u05d7\u05e8 \u05d4\u05ea\u05d9\u05e2\u05d5\u05d3.", + "oauth_error": "\u05d4\u05ea\u05e7\u05d1\u05dc\u05d5 \u05e0\u05ea\u05d5\u05e0\u05d9 \u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9\u05d9\u05dd.", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" + }, + "create_entry": { + "default": "\u05d0\u05d5\u05de\u05ea \u05d1\u05d4\u05e6\u05dc\u05d7\u05d4" + }, + "step": { + "auth": { + "title": "\u05e7\u05d9\u05e9\u05d5\u05e8 \u05d7\u05e9\u05d1\u05d5\u05df \u05d2\u05d5\u05d2\u05dc" + }, + "pick_implementation": { + "title": "\u05d1\u05d7\u05e8 \u05e9\u05d9\u05d8\u05ea \u05d0\u05d9\u05de\u05d5\u05ea" + }, + "reauth_confirm": { + "description": "\u05e9\u05d9\u05dc\u05d5\u05d1 Nest \u05e6\u05e8\u05d9\u05da \u05dc\u05d0\u05de\u05ea \u05de\u05d7\u05d3\u05e9 \u05d0\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05e9\u05dc\u05da", + "title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/hu.json b/homeassistant/components/google/translations/hu.json new file mode 100644 index 00000000000..2c552eca30e --- /dev/null +++ b/homeassistant/components/google/translations/hu.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", + "code_expired": "A hiteles\u00edt\u00e9si k\u00f3d lej\u00e1rt vagy a hiteles\u00edt\u0151 adatok be\u00e1ll\u00edt\u00e1sa \u00e9rv\u00e9nytelen, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg \u00fajra.", + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", + "oauth_error": "\u00c9rv\u00e9nytelen token adatok \u00e9rkeztek.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "progress": { + "exchange": "Google-fi\u00f3kja \u00f6sszekapcsol\u00e1s\u00e1hoz keresse fel a [{url}]({url}) c\u00edmet, \u00e9s \u00edrja be a k\u00f3dot: \n\n{user_code}" + }, + "step": { + "auth": { + "title": "\u00d6sszekapcsol\u00e1s Google-al" + }, + "pick_implementation": { + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" + }, + "reauth_confirm": { + "description": "A Google Napt\u00e1r integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie fi\u00f3kj\u00e1t", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/id.json b/homeassistant/components/google/translations/id.json new file mode 100644 index 00000000000..371fa7551b5 --- /dev/null +++ b/homeassistant/components/google/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "code_expired": "Kode autentikasi kedaluwarsa atau penyiapan kredensial tidak valid, silakan coba lagi.", + "invalid_access_token": "Token akses tidak valid", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "oauth_error": "Menerima respons token yang tidak valid.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "progress": { + "exchange": "Untuk menautkan akun Google Anda, kunjungi [{url}]({url}) dan masukkan kode:\n\n{user_code}" + }, + "step": { + "auth": { + "title": "Tautkan Akun Google" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi Google Kalender perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/it.json b/homeassistant/components/google/translations/it.json new file mode 100644 index 00000000000..4530a690ed6 --- /dev/null +++ b/homeassistant/components/google/translations/it.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "code_expired": "Il codice di autenticazione \u00e8 scaduto o la configurazione delle credenziali non \u00e8 valida, riprova.", + "invalid_access_token": "Token di accesso non valido", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", + "oauth_error": "Ricevuti dati token non validi.", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "progress": { + "exchange": "Per collegare il tuo account Google, visita [{url}]({url}) e inserisci il codice: \n\n {user_code}" + }, + "step": { + "auth": { + "title": "Collega l'account Google" + }, + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + }, + "reauth_confirm": { + "description": "L'integrazione di Google Calendar deve riautenticare il tuo account", + "title": "Autentica nuovamente l'integrazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/ja.json b/homeassistant/components/google/translations/ja.json new file mode 100644 index 00000000000..7eab5abc6f6 --- /dev/null +++ b/homeassistant/components/google/translations/ja.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", + "code_expired": "\u8a8d\u8a3c\u30b3\u30fc\u30c9\u306e\u6709\u52b9\u671f\u9650\u304c\u5207\u308c\u3066\u3044\u308b\u304b\u3001\u8cc7\u683c\u60c5\u5831\u306e\u8a2d\u5b9a\u304c\u7121\u52b9\u3067\u3059\u3002\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", + "invalid_access_token": "\u7121\u52b9\u306a\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3", + "missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002", + "oauth_error": "\u7121\u52b9\u306a\u30c8\u30fc\u30af\u30f3\u30c7\u30fc\u30bf\u3092\u53d7\u4fe1\u3057\u307e\u3057\u305f\u3002", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" + }, + "create_entry": { + "default": "\u6b63\u5e38\u306b\u8a8d\u8a3c\u3055\u308c\u307e\u3057\u305f" + }, + "progress": { + "exchange": "Google\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u30ea\u30f3\u30af\u3059\u308b\u306b\u306f\u3001 [{url}]({url}) \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044: \n\n{user_code}" + }, + "step": { + "auth": { + "title": "Google\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u30ea\u30f3\u30af\u3059\u308b" + }, + "pick_implementation": { + "title": "\u8a8d\u8a3c\u65b9\u6cd5\u306e\u9078\u629e" + }, + "reauth_confirm": { + "description": "Nest\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002", + "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/nl.json b/homeassistant/components/google/translations/nl.json new file mode 100644 index 00000000000..e732fd7cd19 --- /dev/null +++ b/homeassistant/components/google/translations/nl.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "already_in_progress": "Configuratie flow is al in bewerking", + "code_expired": "De authenticatiecode is verlopen of de instelling van de inloggegevens is ongeldig, probeer het opnieuw.", + "invalid_access_token": "Ongeldige toegang token", + "missing_configuration": "Het component is niet geconfigureerd. Volg a.u.b. de documentatie", + "oauth_error": "Ongeldige token data ontvangen", + "reauth_successful": "Herauthentiecatie was succesvol" + }, + "create_entry": { + "default": "Authenticatie succesvol" + }, + "progress": { + "exchange": "Om uw Google-account te koppelen, gaat u naar de [ {url} ]( {url} ) en voert u de code in: \n\n {user_code}" + }, + "step": { + "auth": { + "title": "Link Google Account" + }, + "pick_implementation": { + "title": "Kies een authenticatie methode" + }, + "reauth_confirm": { + "description": "De Google Agenda-integratie moet uw account opnieuw verifi\u00ebren", + "title": "Herauthentiseer integratie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/no.json b/homeassistant/components/google/translations/no.json new file mode 100644 index 00000000000..4065583192c --- /dev/null +++ b/homeassistant/components/google/translations/no.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "code_expired": "Autentiseringskoden er utl\u00f8pt eller p\u00e5loggingsoppsettet er ugyldig. Pr\u00f8v p\u00e5 nytt.", + "invalid_access_token": "Ugyldig tilgangstoken", + "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen", + "oauth_error": "Mottatt ugyldige token data.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "progress": { + "exchange": "Hvis du vil knytte sammen Google-kontoen din, g\u00e5r du til [{url}]({url}) og skriver inn kode:\n\n{user_code}" + }, + "step": { + "auth": { + "title": "Koble til Google-kontoen" + }, + "pick_implementation": { + "title": "Velg godkjenningsmetode" + }, + "reauth_confirm": { + "description": "Google Kalender-integrasjonen m\u00e5 autentisere kontoen din p\u00e5 nytt", + "title": "Godkjenne integrering p\u00e5 nytt" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/pl.json b/homeassistant/components/google/translations/pl.json new file mode 100644 index 00000000000..b4f45c0abe4 --- /dev/null +++ b/homeassistant/components/google/translations/pl.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "code_expired": "Kod uwierzytelniaj\u0105cy wygas\u0142 lub konfiguracja po\u015bwiadcze\u0144 jest nieprawid\u0142owa, spr\u00f3buj ponownie.", + "invalid_access_token": "Niepoprawny token dost\u0119pu", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", + "oauth_error": "Otrzymano nieprawid\u0142owe dane tokena.", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "progress": { + "exchange": "Aby po\u0142\u0105czy\u0107 swoje konto Google, odwied\u017a [{url}]({url}) i wpisz kod: \n\n{user_code}" + }, + "step": { + "auth": { + "title": "Po\u0142\u0105czenie z kontem Google" + }, + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + }, + "reauth_confirm": { + "description": "Integracja Kalendarz Google wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/pt-BR.json b/homeassistant/components/google/translations/pt-BR.json new file mode 100644 index 00000000000..8ab124f1b5c --- /dev/null +++ b/homeassistant/components/google/translations/pt-BR.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "code_expired": "O c\u00f3digo de autentica\u00e7\u00e3o expirou ou a configura\u00e7\u00e3o da credencial \u00e9 inv\u00e1lida. Tente novamente.", + "invalid_access_token": "Token de acesso inv\u00e1lido", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "oauth_error": "Dados de token recebidos inv\u00e1lidos.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "progress": { + "exchange": "Para vincular sua conta do Google, visite o [{url}]({url}) e insira o c\u00f3digo: \n\n {user_code}" + }, + "step": { + "auth": { + "title": "Vincular Conta do Google" + }, + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "reauth_confirm": { + "description": "A integra\u00e7\u00e3o do Google Agenda precisa autenticar novamente sua conta", + "title": "Reautenticar Integra\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/ru.json b/homeassistant/components/google/translations/ru.json new file mode 100644 index 00000000000..a7db4b2f48b --- /dev/null +++ b/homeassistant/components/google/translations/ru.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "code_expired": "\u0421\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043a\u043e\u0434\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438\u0441\u0442\u0435\u043a \u0438\u043b\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u043d\u0435\u0432\u0435\u0440\u043d\u043e, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", + "invalid_access_token": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439.", + "oauth_error": "\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u044b \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u043a\u0435\u043d\u0430.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "create_entry": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "progress": { + "exchange": "\u0427\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Google, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 [{url}]({url}) \u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043e\u0434: \n\n{user_code}" + }, + "step": { + "auth": { + "title": "\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c Google" + }, + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + }, + "reauth_confirm": { + "description": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Google.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/tr.json b/homeassistant/components/google/translations/tr.json new file mode 100644 index 00000000000..9d5fc8d2416 --- /dev/null +++ b/homeassistant/components/google/translations/tr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "code_expired": "Kimlik do\u011frulama kodunun s\u00fcresi doldu veya kimlik bilgisi kurulumu ge\u00e7ersiz, l\u00fctfen tekrar deneyin.", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim anahtar\u0131", + "missing_configuration": "Bile\u015fen yap\u0131land\u0131r\u0131lmam\u0131\u015f. L\u00fctfen belgeleri takip edin.", + "oauth_error": "Ge\u00e7ersiz anahtar verileri al\u0131nd\u0131.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "progress": { + "exchange": "Google hesab\u0131n\u0131z\u0131 ba\u011flamak i\u00e7in [ {url} ]( {url} ) adresini ziyaret edin ve kodu girin: \n\n {user_code}" + }, + "step": { + "auth": { + "title": "Google Hesab\u0131n\u0131 Ba\u011fla" + }, + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7" + }, + "reauth_confirm": { + "description": "Google Takvim entegrasyonunun hesab\u0131n\u0131z\u0131 yeniden do\u011frulamas\u0131 gerekiyor", + "title": "Entegrasyonu Yeniden Do\u011frula" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google/translations/zh-Hant.json b/homeassistant/components/google/translations/zh-Hant.json new file mode 100644 index 00000000000..70e7d81c01e --- /dev/null +++ b/homeassistant/components/google/translations/zh-Hant.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "code_expired": "\u8a8d\u8b49\u78bc\u5df2\u904e\u671f\u6216\u6191\u8b49\u8a2d\u5b9a\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", + "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", + "oauth_error": "\u6536\u5230\u7121\u6548\u7684\u6b0a\u6756\u8cc7\u6599\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "progress": { + "exchange": "\u6b32\u9023\u7d50 Google \u5e33\u865f\u3001\u8acb\u700f\u89bd [{url}]({url}) \u4e26\u8f38\u5165\u4ee3\u78bc\uff1a\n{user_code}" + }, + "step": { + "auth": { + "title": "\u9023\u7d50 Google \u5e33\u865f" + }, + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + }, + "reauth_confirm": { + "description": "Google Calendar \u6574\u5408\u9700\u8981\u91cd\u65b0\u8a8d\u8b49\u60a8\u7684\u5e33\u865f", + "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index fd6aecd5d42..529778ce1b6 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -167,7 +167,6 @@ class AbstractConfig(ABC): def should_2fa(self, state): """If an entity should have 2FA checked.""" - # pylint: disable=no-self-use return True async def async_report_state(self, message, agent_user_id: str): diff --git a/homeassistant/components/google_travel_time/translations/hu.json b/homeassistant/components/google_travel_time/translations/hu.json index 85a15a98e58..03cfdc18b59 100644 --- a/homeassistant/components/google_travel_time/translations/hu.json +++ b/homeassistant/components/google_travel_time/translations/hu.json @@ -11,7 +11,7 @@ "data": { "api_key": "Api kucs", "destination": "C\u00e9l", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "origin": "Eredet" }, "description": "Az eredet \u00e9s a c\u00e9l megad\u00e1sakor megadhat egy vagy t\u00f6bb helyet a pipa karakterrel elv\u00e1lasztva, c\u00edm, sz\u00e9less\u00e9gi / hossz\u00fas\u00e1gi koordin\u00e1t\u00e1k vagy Google helyazonos\u00edt\u00f3 form\u00e1j\u00e1ban. Amikor a helyet megadja egy Google helyazonos\u00edt\u00f3val, akkor az azonos\u00edt\u00f3t el\u0151taggal kell ell\u00e1tni a `hely_azonos\u00edt\u00f3:` sz\u00f3val." diff --git a/homeassistant/components/gpmdp/media_player.py b/homeassistant/components/gpmdp/media_player.py index d156f144eec..b2861e4d96d 100644 --- a/homeassistant/components/gpmdp/media_player.py +++ b/homeassistant/components/gpmdp/media_player.py @@ -11,16 +11,12 @@ import voluptuous as vol from websocket import _exceptions, create_connection from homeassistant.components import configurator -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -44,15 +40,6 @@ DEFAULT_PORT = 5672 GPMDP_CONFIG_FILE = "gpmpd.conf" -SUPPORT_GPMDP = ( - SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SEEK - | SUPPORT_VOLUME_SET - | SUPPORT_PLAY -) - PLAYBACK_DICT = {"0": STATE_PAUSED, "1": STATE_PAUSED, "2": STATE_PLAYING} # Stopped PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -179,6 +166,15 @@ def setup_platform( class GPMDP(MediaPlayerEntity): """Representation of a GPMDP.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.PLAY + ) + def __init__(self, name, url, code): """Initialize the media player.""" @@ -321,11 +317,6 @@ class GPMDP(MediaPlayerEntity): """Return the name of the device.""" return self._name - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_GPMDP - def media_next_track(self): """Send media_next command to media player.""" self.send_gpmdp_msg("playback", "forward", False) diff --git a/homeassistant/components/gree/climate.py b/homeassistant/components/gree/climate.py index 3bff375e320..0d1c7d53f8b 100644 --- a/homeassistant/components/gree/climate.py +++ b/homeassistant/components/gree/climate.py @@ -21,25 +21,17 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_ECO, PRESET_NONE, PRESET_SLEEP, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -68,11 +60,11 @@ from .const import ( _LOGGER = logging.getLogger(__name__) HVAC_MODES = { - Mode.Auto: HVAC_MODE_AUTO, - Mode.Cool: HVAC_MODE_COOL, - Mode.Dry: HVAC_MODE_DRY, - Mode.Fan: HVAC_MODE_FAN_ONLY, - Mode.Heat: HVAC_MODE_HEAT, + Mode.Auto: HVACMode.AUTO, + Mode.Cool: HVACMode.COOL, + Mode.Dry: HVACMode.DRY, + Mode.Fan: HVACMode.FAN_ONLY, + Mode.Heat: HVACMode.HEAT, } HVAC_MODES_REVERSE = {v: k for k, v in HVAC_MODES.items()} @@ -96,13 +88,6 @@ FAN_MODES_REVERSE = {v: k for k, v in FAN_MODES.items()} SWING_MODES = [SWING_OFF, SWING_VERTICAL, SWING_HORIZONTAL, SWING_BOTH] -SUPPORTED_FEATURES = ( - SUPPORT_TARGET_TEMPERATURE - | SUPPORT_FAN_MODE - | SUPPORT_PRESET_MODE - | SUPPORT_SWING_MODE -) - async def async_setup_entry( hass: HomeAssistant, @@ -127,6 +112,13 @@ async def async_setup_entry( class GreeClimateEntity(CoordinatorEntity, ClimateEntity): """Representation of a Gree HVAC device.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.SWING_MODE + ) + def __init__(self, coordinator): """Initialize the Gree device.""" super().__init__(coordinator) @@ -209,7 +201,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): def hvac_mode(self) -> str: """Return the current HVAC mode for the device.""" if not self.coordinator.device.power: - return HVAC_MODE_OFF + return HVACMode.OFF return HVAC_MODES.get(self.coordinator.device.mode) @@ -224,7 +216,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): self._name, ) - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: self.coordinator.device.power = False await self.coordinator.push_state_update() self.async_write_ha_state() @@ -257,7 +249,7 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): def hvac_modes(self) -> list[str]: """Return the HVAC modes support by the device.""" modes = [*HVAC_MODES_REVERSE] - modes.append(HVAC_MODE_OFF) + modes.append(HVACMode.OFF) return modes @property @@ -365,8 +357,3 @@ class GreeClimateEntity(CoordinatorEntity, ClimateEntity): def swing_modes(self) -> list[str]: """Return the swing modes currently supported for this device.""" return SWING_MODES - - @property - def supported_features(self) -> int: - """Return the supported features for this device integration.""" - return SUPPORTED_FEATURES diff --git a/homeassistant/components/gree/manifest.json b/homeassistant/components/gree/manifest.json index cd826f3291d..23a5c654abc 100644 --- a/homeassistant/components/gree/manifest.json +++ b/homeassistant/components/gree/manifest.json @@ -3,7 +3,7 @@ "name": "Gree Climate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gree", - "requirements": ["greeclimate==1.1.0"], + "requirements": ["greeclimate==1.1.1"], "codeowners": ["@cmroche"], "iot_class": "local_polling", "loggers": ["greeclimate"] diff --git a/homeassistant/components/greenwave/light.py b/homeassistant/components/greenwave/light.py index d577c4a3301..a6b018f33ff 100644 --- a/homeassistant/components/greenwave/light.py +++ b/homeassistant/components/greenwave/light.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_HOST @@ -25,8 +25,6 @@ _LOGGER = logging.getLogger(__name__) CONF_VERSION = "version" -SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_VERSION): cv.positive_int} ) @@ -67,6 +65,9 @@ def setup_platform( class GreenwaveLight(LightEntity): """Representation of an Greenwave Reality Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, light, host, token, gatewaydata): """Initialize a Greenwave Reality Light.""" self._did = int(light["did"]) @@ -78,11 +79,6 @@ class GreenwaveLight(LightEntity): self._token = token self._gatewaydata = gatewaydata - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORTED_FEATURES - @property def available(self): """Return True if entity is available.""" diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index 8ddee492834..f47dbcb3b44 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -33,11 +33,9 @@ def basic_group_options_schema( return vol.Schema( { vol.Required(CONF_ENTITIES): entity_selector_without_own_entities( - handler, {"domain": domain, "multiple": True} - ), - vol.Required(CONF_HIDE_MEMBERS, default=False): selector.selector( - {"boolean": {}} + handler, selector.EntitySelectorConfig(domain=domain, multiple=True) ), + vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(), } ) @@ -46,13 +44,11 @@ def basic_group_config_schema(domain: str) -> vol.Schema: """Generate config schema.""" return vol.Schema( { - vol.Required("name"): selector.selector({"text": {}}), - vol.Required(CONF_ENTITIES): selector.selector( - {"entity": {"domain": domain, "multiple": True}} - ), - vol.Required(CONF_HIDE_MEMBERS, default=False): selector.selector( - {"boolean": {}} + vol.Required("name"): selector.TextSelector(), + vol.Required(CONF_ENTITIES): selector.EntitySelector( + selector.EntitySelectorConfig(domain=domain, multiple=True), ), + vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(), } ) @@ -64,14 +60,14 @@ def binary_sensor_options_schema( """Generate options schema.""" return basic_group_options_schema("binary_sensor", handler, options).extend( { - vol.Required(CONF_ALL, default=False): selector.selector({"boolean": {}}), + vol.Required(CONF_ALL, default=False): selector.BooleanSelector(), } ) BINARY_SENSOR_CONFIG_SCHEMA = basic_group_config_schema("binary_sensor").extend( { - vol.Required(CONF_ALL, default=False): selector.selector({"boolean": {}}), + vol.Required(CONF_ALL, default=False): selector.BooleanSelector(), } ) @@ -86,7 +82,7 @@ def light_switch_options_schema( { vol.Required( CONF_ALL, default=False, description={"advanced": True} - ): selector.selector({"boolean": {}}), + ): selector.BooleanSelector(), } ) diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index c2d263ab8ad..c2cd3c6e9d2 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -12,15 +12,8 @@ from homeassistant.components.cover import ( ATTR_TILT_POSITION, DOMAIN, PLATFORM_SCHEMA, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -154,28 +147,28 @@ class CoverGroup(GroupEntity, CoverEntity): features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if features & (SUPPORT_OPEN | SUPPORT_CLOSE): + if features & (CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE): self._covers[KEY_OPEN_CLOSE].add(entity_id) else: self._covers[KEY_OPEN_CLOSE].discard(entity_id) - if features & (SUPPORT_STOP): + if features & (CoverEntityFeature.STOP): self._covers[KEY_STOP].add(entity_id) else: self._covers[KEY_STOP].discard(entity_id) - if features & (SUPPORT_SET_POSITION): + if features & (CoverEntityFeature.SET_POSITION): self._covers[KEY_POSITION].add(entity_id) else: self._covers[KEY_POSITION].discard(entity_id) - if features & (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT): + if features & (CoverEntityFeature.OPEN_TILT | CoverEntityFeature.CLOSE_TILT): self._tilts[KEY_OPEN_CLOSE].add(entity_id) else: self._tilts[KEY_OPEN_CLOSE].discard(entity_id) - if features & (SUPPORT_STOP_TILT): + if features & (CoverEntityFeature.STOP_TILT): self._tilts[KEY_STOP].add(entity_id) else: self._tilts[KEY_STOP].discard(entity_id) - if features & (SUPPORT_SET_TILT_POSITION): + if features & (CoverEntityFeature.SET_TILT_POSITION): self._tilts[KEY_POSITION].add(entity_id) else: self._tilts[KEY_POSITION].discard(entity_id) @@ -320,18 +313,19 @@ class CoverGroup(GroupEntity, CoverEntity): ) supported_features = 0 - supported_features |= ( - SUPPORT_OPEN | SUPPORT_CLOSE if self._covers[KEY_OPEN_CLOSE] else 0 - ) - supported_features |= SUPPORT_STOP if self._covers[KEY_STOP] else 0 - supported_features |= SUPPORT_SET_POSITION if self._covers[KEY_POSITION] else 0 - supported_features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT if self._tilts[KEY_OPEN_CLOSE] else 0 - ) - supported_features |= SUPPORT_STOP_TILT if self._tilts[KEY_STOP] else 0 - supported_features |= ( - SUPPORT_SET_TILT_POSITION if self._tilts[KEY_POSITION] else 0 - ) + if self._covers[KEY_OPEN_CLOSE]: + supported_features |= CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + supported_features |= CoverEntityFeature.STOP if self._covers[KEY_STOP] else 0 + if self._covers[KEY_POSITION]: + supported_features |= CoverEntityFeature.SET_POSITION + if self._tilts[KEY_OPEN_CLOSE]: + supported_features |= ( + CoverEntityFeature.OPEN_TILT | CoverEntityFeature.CLOSE_TILT + ) + if self._tilts[KEY_STOP]: + supported_features |= CoverEntityFeature.STOP_TILT + if self._tilts[KEY_POSITION]: + supported_features |= CoverEntityFeature.SET_TILT_POSITION self._attr_supported_features = supported_features if not self._attr_assumed_state: diff --git a/homeassistant/components/group/fan.py b/homeassistant/components/group/fan.py index 0f39e9de974..4badbe6df51 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -20,10 +20,8 @@ from homeassistant.components.fan import ( SERVICE_SET_PERCENTAGE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -49,7 +47,11 @@ from .util import ( states_equal, ) -SUPPORTED_FLAGS = {SUPPORT_SET_SPEED, SUPPORT_DIRECTION, SUPPORT_OSCILLATE} +SUPPORTED_FLAGS = { + FanEntityFeature.SET_SPEED, + FanEntityFeature.DIRECTION, + FanEntityFeature.OSCILLATE, +} DEFAULT_NAME = "Fan Group" @@ -191,19 +193,25 @@ class FanGroup(GroupEntity, FanEntity): if percentage == 0: await self.async_turn_off() await self._async_call_supported_entities( - SERVICE_SET_PERCENTAGE, SUPPORT_SET_SPEED, {ATTR_PERCENTAGE: percentage} + SERVICE_SET_PERCENTAGE, + FanEntityFeature.SET_SPEED, + {ATTR_PERCENTAGE: percentage}, ) async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" await self._async_call_supported_entities( - SERVICE_OSCILLATE, SUPPORT_OSCILLATE, {ATTR_OSCILLATING: oscillating} + SERVICE_OSCILLATE, + FanEntityFeature.OSCILLATE, + {ATTR_OSCILLATING: oscillating}, ) async def async_set_direction(self, direction: str) -> None: """Set the direction of the fan.""" await self._async_call_supported_entities( - SERVICE_SET_DIRECTION, SUPPORT_DIRECTION, {ATTR_DIRECTION: direction} + SERVICE_SET_DIRECTION, + FanEntityFeature.DIRECTION, + {ATTR_DIRECTION: direction}, ) async def async_turn_on( @@ -268,7 +276,9 @@ class FanGroup(GroupEntity, FanEntity): self._is_on = any(state.state == STATE_ON for state in on_states) self._attr_assumed_state |= not states_equal(on_states) - percentage_states = self._async_states_by_support_flag(SUPPORT_SET_SPEED) + percentage_states = self._async_states_by_support_flag( + FanEntityFeature.SET_SPEED + ) self._percentage = reduce_attribute(percentage_states, ATTR_PERCENTAGE) self._attr_assumed_state |= not attribute_equal( percentage_states, ATTR_PERCENTAGE @@ -286,9 +296,11 @@ class FanGroup(GroupEntity, FanEntity): self._speed_count = 100 self._set_attr_most_frequent( - "_oscillating", SUPPORT_OSCILLATE, ATTR_OSCILLATING + "_oscillating", FanEntityFeature.OSCILLATE, ATTR_OSCILLATING + ) + self._set_attr_most_frequent( + "_direction", FanEntityFeature.DIRECTION, ATTR_DIRECTION ) - self._set_attr_most_frequent("_direction", SUPPORT_DIRECTION, ATTR_DIRECTION) self._supported_features = reduce( ior, [feature for feature in SUPPORTED_FLAGS if self._fans[feature]], 0 diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index aa9e6ad0b53..b9741085c2d 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -27,14 +27,11 @@ from homeassistant.components.light import ( ATTR_WHITE, ATTR_WHITE_VALUE, ATTR_XY_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_ONOFF, PLATFORM_SCHEMA, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -74,7 +71,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) SUPPORT_GROUP_LIGHT = ( - SUPPORT_EFFECT | SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_WHITE_VALUE + LightEntityFeature.EFFECT + | LightEntityFeature.FLASH + | LightEntityFeature.TRANSITION + | SUPPORT_WHITE_VALUE ) _LOGGER = logging.getLogger(__name__) @@ -285,10 +285,10 @@ class LightGroup(GroupEntity, LightEntity): if all_color_modes: # Report the most common color mode, select brightness and onoff last color_mode_count = Counter(itertools.chain(all_color_modes)) - if COLOR_MODE_ONOFF in color_mode_count: - color_mode_count[COLOR_MODE_ONOFF] = -1 - if COLOR_MODE_BRIGHTNESS in color_mode_count: - color_mode_count[COLOR_MODE_BRIGHTNESS] = 0 + if ColorMode.ONOFF in color_mode_count: + color_mode_count[ColorMode.ONOFF] = -1 + if ColorMode.BRIGHTNESS in color_mode_count: + color_mode_count[ColorMode.BRIGHTNESS] = 0 self._attr_color_mode = color_mode_count.most_common(1)[0][0] self._attr_supported_color_modes = None diff --git a/homeassistant/components/group/media_player.py b/homeassistant/components/group/media_player.py index 97d8f51536c..46c019dbc7c 100644 --- a/homeassistant/components/group/media_player.py +++ b/homeassistant/components/group/media_player.py @@ -16,21 +16,8 @@ from homeassistant.components.media_player import ( PLATFORM_SCHEMA, SERVICE_CLEAR_PLAYLIST, SERVICE_PLAY_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -157,36 +144,47 @@ class MediaPlayerGroup(MediaPlayerEntity): return new_features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if new_features & SUPPORT_CLEAR_PLAYLIST: + if new_features & MediaPlayerEntityFeature.CLEAR_PLAYLIST: self._features[KEY_CLEAR_PLAYLIST].add(entity_id) else: self._features[KEY_CLEAR_PLAYLIST].discard(entity_id) - if new_features & (SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK): + if new_features & ( + MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + ): self._features[KEY_TRACKS].add(entity_id) else: self._features[KEY_TRACKS].discard(entity_id) - if new_features & (SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_STOP): + if new_features & ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + ): self._features[KEY_PAUSE_PLAY_STOP].add(entity_id) else: self._features[KEY_PAUSE_PLAY_STOP].discard(entity_id) - if new_features & SUPPORT_PLAY_MEDIA: + if new_features & MediaPlayerEntityFeature.PLAY_MEDIA: self._features[KEY_PLAY_MEDIA].add(entity_id) else: self._features[KEY_PLAY_MEDIA].discard(entity_id) - if new_features & SUPPORT_SEEK: + if new_features & MediaPlayerEntityFeature.SEEK: self._features[KEY_SEEK].add(entity_id) else: self._features[KEY_SEEK].discard(entity_id) - if new_features & SUPPORT_SHUFFLE_SET: + if new_features & MediaPlayerEntityFeature.SHUFFLE_SET: self._features[KEY_SHUFFLE].add(entity_id) else: self._features[KEY_SHUFFLE].discard(entity_id) - if new_features & (SUPPORT_TURN_ON | SUPPORT_TURN_OFF): + if new_features & ( + MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF + ): self._features[KEY_ON_OFF].add(entity_id) else: self._features[KEY_ON_OFF].discard(entity_id) if new_features & ( - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP ): self._features[KEY_VOLUME].add(entity_id) else: @@ -407,32 +405,35 @@ class MediaPlayerGroup(MediaPlayerEntity): self._state = None supported_features = 0 - supported_features |= ( - SUPPORT_CLEAR_PLAYLIST if self._features[KEY_CLEAR_PLAYLIST] else 0 - ) - supported_features |= ( - SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK - if self._features[KEY_TRACKS] - else 0 - ) - supported_features |= ( - SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_STOP - if self._features[KEY_PAUSE_PLAY_STOP] - else 0 - ) - supported_features |= ( - SUPPORT_PLAY_MEDIA if self._features[KEY_PLAY_MEDIA] else 0 - ) - supported_features |= SUPPORT_SEEK if self._features[KEY_SEEK] else 0 - supported_features |= SUPPORT_SHUFFLE_SET if self._features[KEY_SHUFFLE] else 0 - supported_features |= ( - SUPPORT_TURN_ON | SUPPORT_TURN_OFF if self._features[KEY_ON_OFF] else 0 - ) - supported_features |= ( - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP - if self._features[KEY_VOLUME] - else 0 - ) + if self._features[KEY_CLEAR_PLAYLIST]: + supported_features |= MediaPlayerEntityFeature.CLEAR_PLAYLIST + if self._features[KEY_TRACKS]: + supported_features |= ( + MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + ) + if self._features[KEY_PAUSE_PLAY_STOP]: + supported_features |= ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + ) + if self._features[KEY_PLAY_MEDIA]: + supported_features |= MediaPlayerEntityFeature.PLAY_MEDIA + if self._features[KEY_SEEK]: + supported_features |= MediaPlayerEntityFeature.SEEK + if self._features[KEY_SHUFFLE]: + supported_features |= MediaPlayerEntityFeature.SHUFFLE_SET + if self._features[KEY_ON_OFF]: + supported_features |= ( + MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF + ) + if self._features[KEY_VOLUME]: + supported_features |= ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + ) self._supported_features = supported_features self.async_write_ha_state() diff --git a/homeassistant/components/group/translations/bg.json b/homeassistant/components/group/translations/bg.json index d28a170bcd0..584d48e3f28 100644 --- a/homeassistant/components/group/translations/bg.json +++ b/homeassistant/components/group/translations/bg.json @@ -1,9 +1,116 @@ { "config": { "step": { + "binary_sensor": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435", + "name": "\u0418\u043c\u0435" + }, + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + }, + "cover": { + "data": { + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + } + }, + "fan": { + "data": { + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + } + }, + "light": { + "data": { + "all": "\u0412\u0441\u0438\u0447\u043a\u0438 \u043e\u0431\u0435\u043a\u0442\u0438", + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + } + }, + "lock": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435", + "name": "\u0418\u043c\u0435" + } + }, "media_player": { "data": { - "name": "\u0418\u043c\u0435 \u043d\u0430 \u0433\u0440\u0443\u043f\u0430" + "name": "\u0418\u043c\u0435 \u043d\u0430 \u0433\u0440\u0443\u043f\u0430", + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + } + }, + "switch": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435", + "name": "\u0418\u043c\u0435" + }, + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + }, + "user": { + "title": "\u041d\u043e\u0432\u0430 \u0433\u0440\u0443\u043f\u0430" + } + } + }, + "options": { + "step": { + "binary_sensor": { + "data": { + "all": "\u0412\u0441\u0438\u0447\u043a\u0438 \u043e\u0431\u0435\u043a\u0442\u0438", + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "binary_sensor_options": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "cover": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "cover_options": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "fan": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "fan_options": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "light": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "light_options": { + "data": { + "all": "\u0412\u0441\u0438\u0447\u043a\u0438 \u043e\u0431\u0435\u043a\u0442\u0438", + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "lock": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "media_player": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "media_player_options": { + "data": { + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" + } + }, + "switch": { + "data": { + "all": "\u0412\u0441\u0438\u0447\u043a\u0438 \u043e\u0431\u0435\u043a\u0442\u0438", + "entities": "\u0427\u043b\u0435\u043d\u043e\u0432\u0435" } } } diff --git a/homeassistant/components/group/translations/ca.json b/homeassistant/components/group/translations/ca.json index b17d98b5084..bd824fc7428 100644 --- a/homeassistant/components/group/translations/ca.json +++ b/homeassistant/components/group/translations/ca.json @@ -5,18 +5,21 @@ "data": { "all": "Totes les entitats", "entities": "Membres", + "hide_members": "Amaga membres", "name": "Nom" }, "description": "Si \"totes les entitats\" est\u00e0 activat, l'estat del grup estar\u00e0 activat (ON) si tots els membres estan activats. Si \"totes les entitats\" est\u00e0 desactivat, l'estat del grup s'activar\u00e0 si hi ha activat qualsevol membre.", - "title": "Nou grup" + "title": "Afegeix grup" }, "cover": { "data": { "entities": "Membres", + "hide_members": "Amaga membres", "name": "Nom", - "title": "Nou grup" + "title": "Afegeix grup" }, - "description": "Selecciona les opcions del grup" + "description": "Selecciona les opcions del grup", + "title": "Afegeix grup" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Membres", + "hide_members": "Amaga membres", "name": "Nom", - "title": "Nou grup" + "title": "Afegeix grup" }, - "description": "Selecciona les opcions del grup" + "description": "Selecciona les opcions del grup", + "title": "Afegeix grup" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Totes les entitats", "entities": "Membres", + "hide_members": "Amaga membres", "name": "Nom", - "title": "Nou grup" + "title": "Afegeix grup" }, - "description": "Selecciona les opcions del grup" + "description": "Si \"totes les entitats\" est\u00e0 activat, l'estat del grup estar\u00e0 activat (ON) si tots els membres estan activats. Si \"totes les entitats\" est\u00e0 desactivat, l'estat del grup s'activar\u00e0 si hi ha activat qualsevol membre.", + "title": "Afegeix grup" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Selecciona les opcions del grup" }, + "lock": { + "data": { + "entities": "Membres", + "hide_members": "Amaga membres", + "name": "Nom" + }, + "title": "Afegeix grup" + }, "media_player": { "data": { "entities": "Membres", + "hide_members": "Amaga membres", "name": "Nom", - "title": "Nou grup" + "title": "Afegeix grup" }, - "description": "Selecciona les opcions del grup" + "description": "Selecciona les opcions del grup", + "title": "Afegeix grup" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Selecciona les opcions del grup" }, + "switch": { + "data": { + "entities": "Membres", + "hide_members": "Amaga membres", + "name": "Nom" + }, + "title": "Afegeix grup" + }, "user": { "data": { "group_type": "Tipus de grup" }, - "title": "Nou grup" + "description": "Els grups et permeten crear una nova entitat que representa m\u00faltiples entitats del mateix tipus.", + "menu_options": { + "binary_sensor": "Grup de sensors binaris", + "cover": "Grup de cobertes", + "fan": "Grup de ventiladors", + "light": "Grup de llums", + "lock": "Grup de panys", + "media_player": "Grup de reproductors multim\u00e8dia", + "switch": "Grup de commutadors" + }, + "title": "Afegeix grup" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Totes les entitats", + "entities": "Membres", + "hide_members": "Amaga membres" + }, + "description": "Si \"totes les entitats\" est\u00e0 activat, l'estat del grup estar\u00e0 activat (ON) si tots els membres estan activats. Si \"totes les entitats\" est\u00e0 desactivat, l'estat del grup s'activar\u00e0 si hi ha activat qualsevol membre." + }, "binary_sensor_options": { "data": { "all": "Totes les entitats", "entities": "Membres" } }, + "cover": { + "data": { + "entities": "Membres", + "hide_members": "Amaga membres" + } + }, "cover_options": { "data": { "entities": "Membres" } }, + "fan": { + "data": { + "entities": "Membres", + "hide_members": "Amaga membres" + } + }, "fan_options": { "data": { "entities": "Membres" } }, + "light": { + "data": { + "all": "Totes les entitats", + "entities": "Membres", + "hide_members": "Amaga membres" + }, + "description": "Si \"totes les entitats\" est\u00e0 activat, l'estat del grup estar\u00e0 activat (ON) si tots els membres estan activats. Si \"totes les entitats\" est\u00e0 desactivat, l'estat del grup s'activar\u00e0 si hi ha activat qualsevol membre." + }, "light_options": { "data": { + "all": "Totes les entitats", "entities": "Membres" } }, + "lock": { + "data": { + "entities": "Membres", + "hide_members": "Amaga membres" + } + }, + "media_player": { + "data": { + "entities": "Membres", + "hide_members": "Amaga membres" + } + }, "media_player_options": { "data": { "entities": "Membres" } + }, + "switch": { + "data": { + "all": "Totes les entitats", + "entities": "Membres", + "hide_members": "Amaga membres" + }, + "description": "Si \"totes les entitats\" est\u00e0 activat, l'estat del grup estar\u00e0 activat (ON) si tots els membres estan activats. Si \"totes les entitats\" est\u00e0 desactivat, l'estat del grup s'activar\u00e0 si hi ha activat qualsevol membre." } } }, diff --git a/homeassistant/components/group/translations/cs.json b/homeassistant/components/group/translations/cs.json index d35e48058e7..232dbe9e629 100644 --- a/homeassistant/components/group/translations/cs.json +++ b/homeassistant/components/group/translations/cs.json @@ -1,4 +1,200 @@ { + "config": { + "step": { + "binary_sensor": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no" + }, + "description": "Pokud je povoleno \"v\u0161echny entity\", je stav skupiny zapnut\u00fd pouze tehdy, pokud jsou zapnuti v\u0161ichni \u010dlenov\u00e9.\nPokud je mo\u017enost \"v\u0161echny entity\" zak\u00e1z\u00e1na, je stav skupiny zapnut\u00fd, pokud je zapnut\u00fd kter\u00fdkoli \u010dlen.", + "title": "Nov\u00e1 skupina" + }, + "cover": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no", + "title": "Nov\u00e1 skupina" + }, + "description": "Vyberte mo\u017enosti skupiny", + "title": "Nov\u00e1 skupina" + }, + "cover_options": { + "data": { + "entities": "\u010clenov\u00e9 skupiny" + }, + "description": "Vyberte mo\u017enosti skupiny" + }, + "fan": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no", + "title": "Nov\u00e1 skupina" + }, + "description": "Vyberte mo\u017enosti skupiny", + "title": "Nov\u00e1 skupina" + }, + "fan_options": { + "data": { + "entities": "\u010clenov\u00e9 skupiny" + }, + "description": "Vyberte mo\u017enosti skupiny" + }, + "init": { + "data": { + "group_type": "Typ skupiny" + }, + "description": "Vyberte typ skupiny" + }, + "light": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no", + "title": "Nov\u00e1 skupina" + }, + "description": "Pokud je povoleno \"v\u0161echny entity\", je stav skupiny zapnut\u00fd pouze tehdy, pokud jsou zapnuti v\u0161ichni \u010dlenov\u00e9.\nPokud je mo\u017enost \"v\u0161echny entity\" zak\u00e1z\u00e1na, je stav skupiny zapnut\u00fd, pokud je zapnut\u00fd kter\u00fdkoli \u010dlen.", + "title": "Nov\u00e1 skupina" + }, + "light_options": { + "data": { + "entities": "\u010clenov\u00e9 skupiny" + }, + "description": "Vyberte mo\u017enosti skupiny" + }, + "lock": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no" + }, + "title": "Nov\u00e1 skupina" + }, + "media_player": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no", + "title": "Nov\u00e1 skupina" + }, + "description": "Vyberte mo\u017enosti skupiny", + "title": "Nov\u00e1 skupina" + }, + "media_player_options": { + "data": { + "entities": "\u010clenov\u00e9 skupiny" + }, + "description": "Vyberte mo\u017enosti skupiny" + }, + "switch": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny", + "name": "Jm\u00e9no" + }, + "title": "Nov\u00e1 skupina" + }, + "user": { + "data": { + "group_type": "Typ skupiny" + }, + "description": "Vyberte typ skupiny", + "menu_options": { + "binary_sensor": "Skupina bin\u00e1rn\u00edch senzor\u016f", + "cover": "Skupina rolet", + "fan": "Skupina ventil\u00e1tor\u016f", + "light": "Skupina sv\u011btel", + "lock": "Skupina z\u00e1mk\u016f", + "media_player": "Skupina p\u0159ehr\u00e1va\u010d\u016f m\u00e9di\u00ed", + "switch": "Skupina vyp\u00edna\u010d\u016f" + }, + "title": "Nov\u00e1 skupina" + } + } + }, + "options": { + "step": { + "binary_sensor": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + }, + "description": "Pokud je povoleno \"v\u0161echny entity\", je stav skupiny zapnut\u00fd pouze tehdy, pokud jsou zapnuti v\u0161ichni \u010dlenov\u00e9.\nPokud je mo\u017enost \"v\u0161echny entity\" zak\u00e1z\u00e1na, je stav skupiny zapnut\u00fd, pokud je zapnut\u00fd kter\u00fdkoli \u010dlen." + }, + "binary_sensor_options": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9" + } + }, + "cover": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + } + }, + "cover_options": { + "data": { + "entities": "\u010clenov\u00e9" + } + }, + "fan": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + } + }, + "fan_options": { + "data": { + "entities": "\u010clenov\u00e9" + } + }, + "light": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + }, + "description": "Pokud je povoleno \"v\u0161echny entity\", je stav skupiny zapnut\u00fd pouze tehdy, pokud jsou zapnuti v\u0161ichni \u010dlenov\u00e9.\nPokud je mo\u017enost \"v\u0161echny entity\" zak\u00e1z\u00e1na, je stav skupiny zapnut\u00fd, pokud je zapnut\u00fd kter\u00fdkoli \u010dlen." + }, + "light_options": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9" + } + }, + "lock": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + } + }, + "media_player": { + "data": { + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + } + }, + "media_player_options": { + "data": { + "entities": "\u010clenov\u00e9" + } + }, + "switch": { + "data": { + "all": "V\u0161echny entity", + "entities": "\u010clenov\u00e9", + "hide_members": "Skr\u00fdt \u010dleny" + }, + "description": "Pokud je povoleno \"v\u0161echny entity\", je stav skupiny zapnut\u00fd pouze tehdy, pokud jsou zapnuti v\u0161ichni \u010dlenov\u00e9.\nPokud je mo\u017enost \"v\u0161echny entity\" zak\u00e1z\u00e1na, je stav skupiny zapnut\u00fd, pokud je zapnut\u00fd kter\u00fdkoli \u010dlen." + } + } + }, "state": { "_": { "closed": "Zav\u0159eno", diff --git a/homeassistant/components/group/translations/de.json b/homeassistant/components/group/translations/de.json index c06dd2a4a8f..aeaedd3a7cc 100644 --- a/homeassistant/components/group/translations/de.json +++ b/homeassistant/components/group/translations/de.json @@ -5,18 +5,21 @@ "data": { "all": "Alle Entit\u00e4ten", "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", "name": "Name" }, "description": "Wenn \"alle Entit\u00e4ten\" aktiviert ist, ist der Status der Gruppe nur dann eingeschaltet, wenn alle Mitglieder eingeschaltet sind. Wenn \"alle Entit\u00e4ten\" deaktiviert ist, ist der Status der Gruppe eingeschaltet, wenn irgendein Mitglied eingeschaltet ist.", - "title": "Neue Gruppe" + "title": "Gruppe hinzuf\u00fcgen" }, "cover": { "data": { "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", "name": "Name", - "title": "Neue Gruppe" + "title": "Gruppe hinzuf\u00fcgen" }, - "description": "Gruppenoptionen ausw\u00e4hlen" + "description": "Gruppenoptionen ausw\u00e4hlen", + "title": "Gruppe hinzuf\u00fcgen" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", "name": "Name", - "title": "Neue Gruppe" + "title": "Gruppe hinzuf\u00fcgen" }, - "description": "Gruppenoptionen ausw\u00e4hlen" + "description": "Gruppenoptionen ausw\u00e4hlen", + "title": "Gruppe hinzuf\u00fcgen" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Alle Entit\u00e4ten", "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", "name": "Name", - "title": "Neue Gruppe" + "title": "Gruppe hinzuf\u00fcgen" }, - "description": "Gruppenoptionen ausw\u00e4hlen" + "description": "Wenn \"alle Entit\u00e4ten\" aktiviert ist, ist der Status der Gruppe nur dann eingeschaltet, wenn alle Mitglieder eingeschaltet sind. Wenn \"alle Entit\u00e4ten\" deaktiviert ist, ist der Status der Gruppe eingeschaltet, wenn irgendein Mitglied eingeschaltet ist.", + "title": "Gruppe hinzuf\u00fcgen" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Gruppenoptionen ausw\u00e4hlen" }, + "lock": { + "data": { + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", + "name": "Name" + }, + "title": "Gruppe hinzuf\u00fcgen" + }, "media_player": { "data": { "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", "name": "Name", - "title": "Neue Gruppe" + "title": "Gruppe hinzuf\u00fcgen" }, - "description": "Gruppenoptionen ausw\u00e4hlen" + "description": "Gruppenoptionen ausw\u00e4hlen", + "title": "Gruppe hinzuf\u00fcgen" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Gruppenoptionen ausw\u00e4hlen" }, + "switch": { + "data": { + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden", + "name": "Name" + }, + "title": "Gruppe hinzuf\u00fcgen" + }, "user": { "data": { "group_type": "Gruppentyp" }, - "title": "Neue Gruppe" + "description": "Mit Gruppen kannst du eine neue Entit\u00e4t erstellen, die mehrere Entit\u00e4ten desselben Typs darstellt.", + "menu_options": { + "binary_sensor": "Bin\u00e4rer Sensor-Gruppe", + "cover": "Abdeckung-Gruppe", + "fan": "L\u00fcfter-Gruppe", + "light": "Licht-Gruppe", + "lock": "Gruppe sperren", + "media_player": "Media-Player-Gruppe", + "switch": "Gruppe wechseln" + }, + "title": "Gruppe hinzuf\u00fcgen" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Alle Entit\u00e4ten", + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + }, + "description": "Wenn \"alle Entit\u00e4ten\" aktiviert ist, ist der Status der Gruppe nur dann eingeschaltet, wenn alle Mitglieder eingeschaltet sind. Wenn \"alle Entit\u00e4ten\" deaktiviert ist, ist der Status der Gruppe eingeschaltet, wenn irgendein Mitglied eingeschaltet ist." + }, "binary_sensor_options": { "data": { "all": "Alle Entit\u00e4ten", "entities": "Mitglieder" } }, + "cover": { + "data": { + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + } + }, "cover_options": { "data": { "entities": "Mitglieder" } }, + "fan": { + "data": { + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + } + }, "fan_options": { "data": { "entities": "Mitglieder" } }, + "light": { + "data": { + "all": "Alle Entit\u00e4ten", + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + }, + "description": "Wenn \"alle Entit\u00e4ten\" aktiviert ist, ist der Status der Gruppe nur dann eingeschaltet, wenn alle Mitglieder eingeschaltet sind. Wenn \"alle Entit\u00e4ten\" deaktiviert ist, ist der Status der Gruppe eingeschaltet, wenn irgendein Mitglied eingeschaltet ist." + }, "light_options": { "data": { + "all": "Alle Entit\u00e4ten", "entities": "Mitglieder" } }, + "lock": { + "data": { + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + } + }, + "media_player": { + "data": { + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + } + }, "media_player_options": { "data": { "entities": "Mitglieder" } + }, + "switch": { + "data": { + "all": "Alle Entit\u00e4ten", + "entities": "Mitglieder", + "hide_members": "Mitglieder ausblenden" + }, + "description": "Wenn \"alle Entit\u00e4ten\" aktiviert ist, ist der Status der Gruppe nur dann eingeschaltet, wenn alle Mitglieder eingeschaltet sind. Wenn \"alle Entit\u00e4ten\" deaktiviert ist, ist der Status der Gruppe eingeschaltet, wenn irgendein Mitglied eingeschaltet ist." } } }, diff --git a/homeassistant/components/group/translations/el.json b/homeassistant/components/group/translations/el.json index bebfb8f1cb8..edd67034de8 100644 --- a/homeassistant/components/group/translations/el.json +++ b/homeassistant/components/group/translations/el.json @@ -5,6 +5,7 @@ "data": { "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", "name": "\u038c\u03bd\u03bf\u03bc\u03b1" }, "description": "\u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\", \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03bc\u03cc\u03bd\u03bf \u03b5\u03ac\u03bd \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03bc\u03ad\u03bb\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b1. \u0395\u03ac\u03bd \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\" \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b5\u03ac\u03bd \u03bf\u03c0\u03bf\u03b9\u03bf\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03bc\u03ad\u03bb\u03bf\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf.", @@ -13,10 +14,12 @@ "cover": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", "entities": "\u039c\u03ad\u03bb\u03b7 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" }, + "lock": { + "data": { + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" + }, "media_player": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" }, + "switch": { + "data": { + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" + }, "user": { "data": { "group_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" }, - "title": "\u039d\u03ad\u03b1 \u03bf\u03bc\u03ac\u03b4\u03b1" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03cd\u03c0\u03bf \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2", + "menu_options": { + "binary_sensor": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03b4\u03c5\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd", + "cover": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03ba\u03b1\u03bb\u03c5\u03bc\u03bc\u03ac\u03c4\u03c9\u03bd", + "fan": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03b1\u03bd\u03b5\u03bc\u03b9\u03c3\u03c4\u03ae\u03c1\u03c9\u03bd", + "light": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03c6\u03ce\u03c4\u03c9\u03bd", + "lock": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2", + "media_player": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd", + "switch": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" + }, + "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd" + }, + "description": "\u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\", \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03bc\u03cc\u03bd\u03bf \u03b5\u03ac\u03bd \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03bc\u03ad\u03bb\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b1. \u0395\u03ac\u03bd \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\" \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b5\u03ac\u03bd \u03bf\u03c0\u03bf\u03b9\u03bf\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03bc\u03ad\u03bb\u03bf\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf." + }, "binary_sensor_options": { "data": { "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", "entities": "\u039c\u03ad\u03bb\u03b7" } }, + "cover": { + "data": { + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd" + } + }, "cover_options": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7" } }, + "fan": { + "data": { + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd" + } + }, "fan_options": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7" } }, + "light": { + "data": { + "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd" + }, + "description": "\u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\", \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03bc\u03cc\u03bd\u03bf \u03b5\u03ac\u03bd \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03bc\u03ad\u03bb\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b1. \u0395\u03ac\u03bd \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\" \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b5\u03ac\u03bd \u03bf\u03c0\u03bf\u03b9\u03bf\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03bc\u03ad\u03bb\u03bf\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf." + }, "light_options": { "data": { + "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", "entities": "\u039c\u03ad\u03bb\u03b7" } }, + "lock": { + "data": { + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + } + }, + "media_player": { + "data": { + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd" + } + }, "media_player_options": { "data": { "entities": "\u039c\u03ad\u03bb\u03b7" } + }, + "switch": { + "data": { + "all": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", + "entities": "\u039c\u03ad\u03bb\u03b7", + "hide_members": "\u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03bc\u03b5\u03bb\u03ce\u03bd" + }, + "description": "\u0395\u03ac\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\", \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03bc\u03cc\u03bd\u03bf \u03b5\u03ac\u03bd \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03bc\u03ad\u03bb\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b1. \u0395\u03ac\u03bd \u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\" \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7, \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b5\u03ac\u03bd \u03bf\u03c0\u03bf\u03b9\u03bf\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03bc\u03ad\u03bb\u03bf\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf." } } }, diff --git a/homeassistant/components/group/translations/en.json b/homeassistant/components/group/translations/en.json index a67d23b812d..f7b3942c696 100644 --- a/homeassistant/components/group/translations/en.json +++ b/homeassistant/components/group/translations/en.json @@ -15,26 +15,57 @@ "data": { "entities": "Members", "hide_members": "Hide members", - "name": "Name" + "name": "Name", + "title": "Add Group" }, + "description": "Select group options", "title": "Add Group" }, + "cover_options": { + "data": { + "entities": "Group members" + }, + "description": "Select group options" + }, "fan": { "data": { "entities": "Members", "hide_members": "Hide members", - "name": "Name" + "name": "Name", + "title": "Add Group" }, + "description": "Select group options", "title": "Add Group" }, + "fan_options": { + "data": { + "entities": "Group members" + }, + "description": "Select group options" + }, + "init": { + "data": { + "group_type": "Group type" + }, + "description": "Select group type" + }, "light": { "data": { + "all": "All entities", "entities": "Members", "hide_members": "Hide members", - "name": "Name" + "name": "Name", + "title": "Add Group" }, + "description": "If \"all entities\" is enabled, the group's state is on only if all members are on. If \"all entities\" is disabled, the group's state is on if any member is on.", "title": "Add Group" }, + "light_options": { + "data": { + "entities": "Group members" + }, + "description": "Select group options" + }, "lock": { "data": { "entities": "Members", @@ -47,10 +78,18 @@ "data": { "entities": "Members", "hide_members": "Hide members", - "name": "Name" + "name": "Name", + "title": "Add Group" }, + "description": "Select group options", "title": "Add Group" }, + "media_player_options": { + "data": { + "entities": "Group members" + }, + "description": "Select group options" + }, "switch": { "data": { "entities": "Members", @@ -60,6 +99,9 @@ "title": "Add Group" }, "user": { + "data": { + "group_type": "Group type" + }, "description": "Groups allow you to create a new entity that represents multiple entities of the same type.", "menu_options": { "binary_sensor": "Binary sensor group", @@ -84,18 +126,34 @@ }, "description": "If \"all entities\" is enabled, the group's state is on only if all members are on. If \"all entities\" is disabled, the group's state is on if any member is on." }, + "binary_sensor_options": { + "data": { + "all": "All entities", + "entities": "Members" + } + }, "cover": { "data": { "entities": "Members", "hide_members": "Hide members" } }, + "cover_options": { + "data": { + "entities": "Members" + } + }, "fan": { "data": { "entities": "Members", "hide_members": "Hide members" } }, + "fan_options": { + "data": { + "entities": "Members" + } + }, "light": { "data": { "all": "All entities", @@ -104,6 +162,12 @@ }, "description": "If \"all entities\" is enabled, the group's state is on only if all members are on. If \"all entities\" is disabled, the group's state is on if any member is on." }, + "light_options": { + "data": { + "all": "All entities", + "entities": "Members" + } + }, "lock": { "data": { "entities": "Members", @@ -116,6 +180,11 @@ "hide_members": "Hide members" } }, + "media_player_options": { + "data": { + "entities": "Members" + } + }, "switch": { "data": { "all": "All entities", diff --git a/homeassistant/components/group/translations/et.json b/homeassistant/components/group/translations/et.json index c45094aeedd..d0d1466e26b 100644 --- a/homeassistant/components/group/translations/et.json +++ b/homeassistant/components/group/translations/et.json @@ -5,6 +5,7 @@ "data": { "all": "K\u00f5ik olemid", "entities": "Liikmed", + "hide_members": "Peida grupi liikmed", "name": "Nimi" }, "description": "Kui \"k\u00f5ik olemid\" on lubatud,on r\u00fchma olek sees ainult siis kui k\u00f5ik liikmed on sisse l\u00fclitatud. Kui \"k\u00f5ik olemid\" on keelatud, on r\u00fchma olek sees kui m\u00f5ni liige on sisse l\u00fclitatud.", @@ -13,10 +14,12 @@ "cover": { "data": { "entities": "Liikmed", + "hide_members": "Peida grupi liikmed", "name": "Nimi", "title": "Uus r\u00fchm" }, - "description": "R\u00fchmasuvandite valimine" + "description": "R\u00fchmasuvandite valimine", + "title": "Uus grupp" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Liikmed", + "hide_members": "Peida grupi liikmed", "name": "Nimi", "title": "Uus r\u00fchm" }, - "description": "R\u00fchmasuvandite valimine" + "description": "R\u00fchmasuvandite valimine", + "title": "Uus grupp" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "K\u00f5ik olemid", "entities": "Liikmed", + "hide_members": "Peida grupi liikmed", "name": "Nimi", "title": "Uus r\u00fchm" }, - "description": "R\u00fchmasuvandite valimine" + "description": "Kui on vlitud \"K\u00f5ik olemid\" siis on grupi olek Sees kui k\u00f5ik olemid on sees. Kui \"K\u00f5ik olemid\" on keelatud siis on grupi olek Sees kui m\u00f5ni olem on sisse l\u00fclitatud.", + "title": "Uus grupp" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "R\u00fchmasuvandite valimine" }, + "lock": { + "data": { + "entities": "Liikmed", + "hide_members": "Peida liikmed", + "name": "Nimi" + }, + "title": "Uus grupp" + }, "media_player": { "data": { "entities": "Liikmed", + "hide_members": "Peida grupi liikmed", "name": "Nimi", "title": "Uus r\u00fchm" }, - "description": "R\u00fchmasuvandite valimine" + "description": "R\u00fchmasuvandite valimine", + "title": "Uus grupp" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "R\u00fchmasuvandite valimine" }, + "switch": { + "data": { + "entities": "Liikmed", + "hide_members": "Peida grupi liikmed", + "name": "Nimi" + }, + "title": "Uus grupp" + }, "user": { "data": { "group_type": "R\u00fchma t\u00fc\u00fcp" }, - "title": "Uus r\u00fchm" + "description": "R\u00fchmad v\u00f5imaldavad luua uue olemi,mis esindab mitut sama t\u00fc\u00fcpi olemit.", + "menu_options": { + "binary_sensor": "Olekuandurite r\u00fchm", + "cover": "Aknakatete r\u00fchm", + "fan": "Ventilaatorite r\u00fchm", + "light": "Valgustite r\u00fchm", + "lock": "Lukusta grupp", + "media_player": "Meediumipleieri r\u00fchm", + "switch": "Grupi vahetamine" + }, + "title": "Lisa grupp" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "K\u00f5ik olemid", + "entities": "Grupi liikmed", + "hide_members": "Peida grupi liikmed" + }, + "description": "Kui \"k\u00f5ik olemid\" on lubatud,on r\u00fchma olek sees ainult siis kui k\u00f5ik liikmed on sisse l\u00fclitatud. Kui \"k\u00f5ik olemid\" on keelatud, on r\u00fchma olek sees kui m\u00f5ni liige on sisse l\u00fclitatud." + }, "binary_sensor_options": { "data": { "all": "K\u00f5ik olemid", "entities": "Liikmed" } }, + "cover": { + "data": { + "entities": "Grupi liikmed", + "hide_members": "Peida grupi liikmed" + } + }, "cover_options": { "data": { "entities": "Liikmed" } }, + "fan": { + "data": { + "entities": "Grupi liikmed", + "hide_members": "Peida grupi liikmed" + } + }, "fan_options": { "data": { "entities": "Liikmed" } }, + "light": { + "data": { + "all": "K\u00f5ik olemid", + "entities": "Grupi liikmed", + "hide_members": "Peida grupi liikmed" + }, + "description": "Kui \"k\u00f5ik olemid\" on lubatud,on r\u00fchma olek sees ainult siis kui k\u00f5ik liikmed on sisse l\u00fclitatud. Kui \"k\u00f5ik olemid\" on keelatud, on r\u00fchma olek sees kui m\u00f5ni liige on sisse l\u00fclitatud." + }, "light_options": { "data": { + "all": "K\u00f5ik olemid", "entities": "Liikmed" } }, + "lock": { + "data": { + "entities": "Liikmed", + "hide_members": "Peida liikmed" + } + }, + "media_player": { + "data": { + "entities": "Grupi liikmed", + "hide_members": "Peida grupi liikmed" + } + }, "media_player_options": { "data": { "entities": "Liikmed" } + }, + "switch": { + "data": { + "all": "K\u00f5ik olemid", + "entities": "Liikmed", + "hide_members": "Peida grupi liikmed" + }, + "description": "Kui \"k\u00f5ik olemid\" on lubatud,on r\u00fchma olek sees ainult siis kui k\u00f5ik liikmed on sisse l\u00fclitatud. Kui \"k\u00f5ik olemid\" on keelatud, on r\u00fchma olek sees kui m\u00f5ni liige on sisse l\u00fclitatud." } } }, diff --git a/homeassistant/components/group/translations/fr.json b/homeassistant/components/group/translations/fr.json index 8e8b5ee4233..e8b74e2f374 100644 --- a/homeassistant/components/group/translations/fr.json +++ b/homeassistant/components/group/translations/fr.json @@ -5,18 +5,21 @@ "data": { "all": "Toutes les entit\u00e9s", "entities": "Membres", + "hide_members": "Cacher les membres", "name": "Nom" }, "description": "Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est activ\u00e9, l'\u00e9tat du groupe n'est activ\u00e9 que si tous les membres sont activ\u00e9s. Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est d\u00e9sactiv\u00e9, l'\u00e9tat du groupe est activ\u00e9 si au moins un membre est activ\u00e9.", - "title": "Nouveau groupe" + "title": "Ajouter un groupe" }, "cover": { "data": { "entities": "Membres", + "hide_members": "Cacher les membres", "name": "Nom", - "title": "Nouveau groupe" + "title": "Ajouter un groupe" }, - "description": "S\u00e9lectionnez les options du groupe" + "description": "S\u00e9lectionnez les options du groupe", + "title": "Ajouter un groupe" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Membres", + "hide_members": "Cacher les membres", "name": "Nom", - "title": "Nouveau groupe" + "title": "Ajouter un groupe" }, - "description": "S\u00e9lectionnez les options du groupe" + "description": "S\u00e9lectionnez les options du groupe", + "title": "Ajouter un groupe" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Toutes les entit\u00e9s", "entities": "Membres", + "hide_members": "Cacher les membres", "name": "Nom", - "title": "Nouveau groupe" + "title": "Ajouter un groupe" }, - "description": "S\u00e9lectionnez les options du groupe" + "description": "Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est activ\u00e9, l'\u00e9tat du groupe n'est activ\u00e9 que si tous les membres sont activ\u00e9s. Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est d\u00e9sactiv\u00e9, l'\u00e9tat du groupe est activ\u00e9 si au moins un membre est activ\u00e9.", + "title": "Ajouter un groupe" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "S\u00e9lectionnez les options du groupe" }, + "lock": { + "data": { + "entities": "Membres", + "hide_members": "Cacher les membres", + "name": "Nom" + }, + "title": "Ajouter un groupe" + }, "media_player": { "data": { "entities": "Membres", + "hide_members": "Cacher les membres", "name": "Nom", - "title": "Nouveau groupe" + "title": "Ajouter un groupe" }, - "description": "S\u00e9lectionnez les options du groupe" + "description": "S\u00e9lectionnez les options du groupe", + "title": "Ajouter un groupe" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "S\u00e9lectionnez les options du groupe" }, + "switch": { + "data": { + "entities": "Membres", + "hide_members": "Cacher les membres", + "name": "Nom" + }, + "title": "Ajouter un groupe" + }, "user": { "data": { "group_type": "Type de groupe" }, - "title": "Nouveau groupe" + "description": "Les groupes vous permettent de cr\u00e9er une nouvelle entit\u00e9 repr\u00e9sentant plusieurs entit\u00e9s d'un m\u00eame type.", + "menu_options": { + "binary_sensor": "Groupe de capteurs binaires", + "cover": "Groupe de fermetures", + "fan": "Groupe de ventilateurs", + "light": "Groupe de lumi\u00e8res", + "lock": "Groupe de verrous", + "media_player": "Groupe de lecteurs multim\u00e9dia", + "switch": "Groupe d'interrupteurs" + }, + "title": "Ajouter un groupe" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Toutes les entit\u00e9s", + "entities": "Membres", + "hide_members": "Cacher les membres" + }, + "description": "Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est activ\u00e9, l'\u00e9tat du groupe n'est activ\u00e9 que si tous les membres sont activ\u00e9s. Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est d\u00e9sactiv\u00e9, l'\u00e9tat du groupe est activ\u00e9 si au moins un membre est activ\u00e9." + }, "binary_sensor_options": { "data": { "all": "Toutes les entit\u00e9s", "entities": "Membres" } }, + "cover": { + "data": { + "entities": "Membres", + "hide_members": "Cacher les membres" + } + }, "cover_options": { "data": { "entities": "Membres" } }, + "fan": { + "data": { + "entities": "Membres", + "hide_members": "Cacher les membres" + } + }, "fan_options": { "data": { "entities": "Membres" } }, + "light": { + "data": { + "all": "Toutes les entit\u00e9s", + "entities": "Membres", + "hide_members": "Cacher les membres" + }, + "description": "Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est activ\u00e9, l'\u00e9tat du groupe n'est activ\u00e9 que si tous les membres sont activ\u00e9s. Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est d\u00e9sactiv\u00e9, l'\u00e9tat du groupe est activ\u00e9 si au moins un membre est activ\u00e9." + }, "light_options": { "data": { + "all": "Toutes les entit\u00e9s", "entities": "Membres" } }, + "lock": { + "data": { + "entities": "Membres", + "hide_members": "Cacher les membres" + } + }, + "media_player": { + "data": { + "entities": "Membres", + "hide_members": "Cacher les membres" + } + }, "media_player_options": { "data": { "entities": "Membres" } + }, + "switch": { + "data": { + "all": "Toutes les entit\u00e9s", + "entities": "Membres", + "hide_members": "Cacher les membres" + }, + "description": "Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est activ\u00e9, l'\u00e9tat du groupe n'est activ\u00e9 que si tous les membres sont activ\u00e9s. Si \u00ab\u00a0toutes les entit\u00e9s\u00a0\u00bb est d\u00e9sactiv\u00e9, l'\u00e9tat du groupe est activ\u00e9 si au moins un membre est activ\u00e9." } } }, diff --git a/homeassistant/components/group/translations/he.json b/homeassistant/components/group/translations/he.json index 06c1a8e0fa8..ab7a90cb699 100644 --- a/homeassistant/components/group/translations/he.json +++ b/homeassistant/components/group/translations/he.json @@ -5,6 +5,7 @@ "data": { "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", "name": "\u05e9\u05dd" }, "description": "\u05d0\u05dd \u05d4\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d6\u05de\u05d9\u05e0\u05d4, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05e8\u05e7 \u05d0\u05dd \u05db\u05dc \u05d4\u05d7\u05d1\u05e8\u05d9\u05dd \u05e4\u05d5\u05e2\u05dc\u05d9\u05dd. \u05d0\u05dd \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d0\u05d9\u05e0\u05d5 \u05d6\u05de\u05d9\u05df, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05dd \u05d7\u05d1\u05e8 \u05db\u05dc\u05e9\u05d4\u05d5 \u05e4\u05d5\u05e2\u05dc.", @@ -13,10 +14,12 @@ "cover": { "data": { "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", "name": "\u05e9\u05dd", "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" + "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4", + "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", "name": "\u05e9\u05dd", "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" + "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4", + "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", "name": "\u05e9\u05dd", "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" + "description": "\u05d0\u05dd \u05d4\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d6\u05de\u05d9\u05e0\u05d4, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05e8\u05e7 \u05d0\u05dd \u05db\u05dc \u05d4\u05d7\u05d1\u05e8\u05d9\u05dd \u05e4\u05d5\u05e2\u05dc\u05d9\u05dd. \u05d0\u05dd \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d0\u05d9\u05e0\u05d5 \u05d6\u05de\u05d9\u05df, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05dd \u05d7\u05d1\u05e8 \u05db\u05dc\u05e9\u05d4\u05d5 \u05e4\u05d5\u05e2\u05dc.", + "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" }, + "lock": { + "data": { + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", + "name": "\u05e9\u05dd" + }, + "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" + }, "media_player": { "data": { "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", "name": "\u05e9\u05dd", "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, - "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" + "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4", + "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" }, "media_player_options": { "data": { @@ -72,47 +90,112 @@ }, "description": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05e7\u05d1\u05d5\u05e6\u05d4" }, + "switch": { + "data": { + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd", + "name": "\u05e9\u05dd" + }, + "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" + }, "user": { "data": { "group_type": "\u05e1\u05d5\u05d2 \u05e7\u05d1\u05d5\u05e6\u05d4" }, + "description": "\u05e7\u05d1\u05d5\u05e6\u05d5\u05ea \u05de\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05da \u05dc\u05d9\u05e6\u05d5\u05e8 \u05d9\u05e9\u05d5\u05ea \u05d7\u05d3\u05e9\u05d4 \u05d4\u05de\u05d9\u05d9\u05e6\u05d2\u05ea \u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05de\u05e8\u05d5\u05d1\u05d5\u05ea \u05de\u05d0\u05d5\u05ea\u05d5 \u05e1\u05d5\u05d2.", + "menu_options": { + "binary_sensor": "\u05e7\u05d1\u05d5\u05e6\u05ea \u05d7\u05d9\u05d9\u05e9\u05e0\u05d9\u05dd \u05d1\u05d9\u05e0\u05d0\u05e8\u05d9\u05d9\u05dd", + "cover": "\u05e7\u05d1\u05d5\u05e6\u05ea \u05d5\u05d9\u05dc\u05d5\u05e0\u05d5\u05ea", + "fan": "\u05e7\u05d1\u05d5\u05e6\u05ea \u05d0\u05d9\u05d5\u05d5\u05e8\u05d5\u05e8", + "light": "\u05e7\u05d1\u05d5\u05e6\u05ea \u05ea\u05d0\u05d5\u05e8\u05d4", + "media_player": "\u05e7\u05d1\u05d5\u05e6\u05ea \u05e0\u05d2\u05e0\u05d9 \u05de\u05d3\u05d9\u05d4" + }, "title": "\u05e7\u05d1\u05d5\u05e6\u05d4 \u05d7\u05d3\u05e9\u05d4" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + }, + "description": "\u05d0\u05dd \u05d4\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d6\u05de\u05d9\u05e0\u05d4, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05e8\u05e7 \u05d0\u05dd \u05db\u05dc \u05d4\u05d7\u05d1\u05e8\u05d9\u05dd \u05e4\u05d5\u05e2\u05dc\u05d9\u05dd. \u05d0\u05dd \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d0\u05d9\u05e0\u05d5 \u05d6\u05de\u05d9\u05df, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05dd \u05d7\u05d1\u05e8 \u05db\u05dc\u05e9\u05d4\u05d5 \u05e4\u05d5\u05e2\u05dc." + }, "binary_sensor_options": { "data": { "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd" } }, + "cover": { + "data": { + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + } + }, "cover_options": { "data": { "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd" } }, + "fan": { + "data": { + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + } + }, "fan_options": { "data": { "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd" } }, + "light": { + "data": { + "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + }, + "description": "\u05d0\u05dd \u05d4\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d6\u05de\u05d9\u05e0\u05d4, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05e8\u05e7 \u05d0\u05dd \u05db\u05dc \u05d4\u05d7\u05d1\u05e8\u05d9\u05dd \u05e4\u05d5\u05e2\u05dc\u05d9\u05dd. \u05d0\u05dd \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d0\u05d9\u05e0\u05d5 \u05d6\u05de\u05d9\u05df, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05dd \u05d7\u05d1\u05e8 \u05db\u05dc\u05e9\u05d4\u05d5 \u05e4\u05d5\u05e2\u05dc." + }, "light_options": { "data": { + "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd" } }, + "lock": { + "data": { + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + } + }, + "media_player": { + "data": { + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + } + }, "media_player_options": { "data": { "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd" } + }, + "switch": { + "data": { + "all": "\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea", + "entities": "\u05d7\u05d1\u05e8\u05d9\u05dd", + "hide_members": "\u05d4\u05e1\u05ea\u05e8\u05ea \u05d7\u05d1\u05e8\u05d9\u05dd" + }, + "description": "\u05d0\u05dd \u05d4\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d6\u05de\u05d9\u05e0\u05d4, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05e8\u05e7 \u05d0\u05dd \u05db\u05dc \u05d4\u05d7\u05d1\u05e8\u05d9\u05dd \u05e4\u05d5\u05e2\u05dc\u05d9\u05dd. \u05d0\u05dd \"\u05db\u05dc \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea\" \u05d0\u05d9\u05e0\u05d5 \u05d6\u05de\u05d9\u05df, \u05de\u05e6\u05d1 \u05d4\u05e7\u05d1\u05d5\u05e6\u05d4 \u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05dd \u05d7\u05d1\u05e8 \u05db\u05dc\u05e9\u05d4\u05d5 \u05e4\u05d5\u05e2\u05dc." } } }, "state": { "_": { - "closed": "\u05e1\u05d2\u05d5\u05e8", + "closed": "\u05e0\u05e1\u05d2\u05e8", "home": "\u05d1\u05d1\u05d9\u05ea", "locked": "\u05e0\u05e2\u05d5\u05dc", "not_home": "\u05d1\u05d7\u05d5\u05e5", diff --git a/homeassistant/components/group/translations/hu.json b/homeassistant/components/group/translations/hu.json index 08c7d5e5afb..ba368532cff 100644 --- a/homeassistant/components/group/translations/hu.json +++ b/homeassistant/components/group/translations/hu.json @@ -5,18 +5,21 @@ "data": { "all": "Minden entit\u00e1s", "entities": "A csoport tagjai", - "name": "N\u00e9v" + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s" }, "description": "Ha \u201eMinden entit\u00e1s\u201d enged\u00e9lyezve van, a csoport \u00e1llapota csak akkor van bekapcsolva, ha minden tag \u00e1llapota bekapcsolt. Ha \u201eMinden entit\u00e1s\u201d le van tiltva, a csoport \u00e1llapota akkor van bekapcsolva, ha b\u00e1rmelyik tag bekapcsolt \u00e1llapotban van.", - "title": "\u00daj csoport" + "title": "Csoport hozz\u00e1ad\u00e1sa" }, "cover": { "data": { "entities": "A csoport tagjai", - "name": "Csoport neve", - "title": "\u00daj csoport" + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" + "description": "Csoport be\u00e1ll\u00edt\u00e1sai", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "A csoport tagjai", - "name": "A csoport elnevez\u00e9se", - "title": "\u00daj csoport" + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" + "description": "Csoport be\u00e1ll\u00edt\u00e1sai", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Minden entit\u00e1s", "entities": "A csoport tagjai", - "name": "A csoport elnevez\u00e9se", - "title": "\u00daj csoport" + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" + "description": "Ha \u201eMinden entit\u00e1s\u201d enged\u00e9lyezve van, a csoport \u00e1llapota csak akkor van bekapcsolva, ha minden tag \u00e1llapota bekapcsolt. Ha \u201eMinden entit\u00e1s\u201d le van tiltva, a csoport \u00e1llapota akkor van bekapcsolva, ha b\u00e1rmelyik tag bekapcsolt \u00e1llapotban van.", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Csoport be\u00e1ll\u00edt\u00e1sai" }, + "lock": { + "data": { + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s" + }, + "title": "Csoport hozz\u00e1ad\u00e1sa" + }, "media_player": { "data": { "entities": "A csoport tagjai", - "name": "A csoport elnevez\u00e9se", - "title": "\u00daj csoport" + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, - "description": "Csoport be\u00e1ll\u00edt\u00e1sai" + "description": "Csoport be\u00e1ll\u00edt\u00e1sai", + "title": "Csoport hozz\u00e1ad\u00e1sa" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Csoport be\u00e1ll\u00edt\u00e1sai" }, + "switch": { + "data": { + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se", + "name": "Elnevez\u00e9s" + }, + "title": "Csoport hozz\u00e1ad\u00e1sa" + }, "user": { "data": { "group_type": "Csoport t\u00edpusa" }, - "title": "\u00daj csoport" + "description": "A csoportok lehet\u0151v\u00e9 teszik egy \u00faj entit\u00e1s l\u00e9trehoz\u00e1s\u00e1t, amely t\u00f6bb azonos t\u00edpus\u00fa entit\u00e1st k\u00e9pvisel.", + "menu_options": { + "binary_sensor": "Bin\u00e1ris \u00e9rz\u00e9kel\u0151 csoport", + "cover": "Red\u0151ny csoport", + "fan": "Ventil\u00e1tor csoport", + "light": "L\u00e1mpa csoport", + "lock": "Csoport z\u00e1rol\u00e1sa", + "media_player": "M\u00e9dialej\u00e1tsz\u00f3 csoport", + "switch": "Kapcsol\u00f3csoport" + }, + "title": "Csoport hozz\u00e1ad\u00e1sa" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Minden entit\u00e1s", + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + }, + "description": "Ha \u201eMinden entit\u00e1s\u201d enged\u00e9lyezve van, a csoport \u00e1llapota csak akkor van bekapcsolva, ha minden tag \u00e1llapota bekapcsolt. Ha \u201eMinden entit\u00e1s\u201d le van tiltva, a csoport \u00e1llapota akkor van bekapcsolva, ha b\u00e1rmelyik tag bekapcsolt \u00e1llapotban van." + }, "binary_sensor_options": { "data": { "all": "Minden entit\u00e1s", "entities": "A csoport tagjai" } }, + "cover": { + "data": { + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + } + }, "cover_options": { "data": { "entities": "A csoport tagjai" } }, + "fan": { + "data": { + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + } + }, "fan_options": { "data": { "entities": "A csoport tagjai" } }, + "light": { + "data": { + "all": "Minden entit\u00e1s", + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + }, + "description": "Ha \u201eMinden entit\u00e1s\u201d enged\u00e9lyezve van, a csoport \u00e1llapota csak akkor van bekapcsolva, ha minden tag \u00e1llapota bekapcsolt. Ha \u201eMinden entit\u00e1s\u201d le van tiltva, a csoport \u00e1llapota akkor van bekapcsolva, ha b\u00e1rmelyik tag bekapcsolt \u00e1llapotban van." + }, "light_options": { "data": { + "all": "Minden entit\u00e1s", "entities": "A csoport tagjai" } }, + "lock": { + "data": { + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + } + }, + "media_player": { + "data": { + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + } + }, "media_player_options": { "data": { "entities": "A csoport tagjai" } + }, + "switch": { + "data": { + "all": "Minden entit\u00e1s", + "entities": "A csoport tagjai", + "hide_members": "Tagok elrejt\u00e9se" + }, + "description": "Ha \u201eMinden entit\u00e1s\u201d enged\u00e9lyezve van, a csoport \u00e1llapota csak akkor van bekapcsolva, ha minden tag \u00e1llapota bekapcsolt. Ha \u201eMinden entit\u00e1s\u201d le van tiltva, a csoport \u00e1llapota akkor van bekapcsolva, ha b\u00e1rmelyik tag bekapcsolt \u00e1llapotban van." } } }, diff --git a/homeassistant/components/group/translations/id.json b/homeassistant/components/group/translations/id.json index 2a92526d644..c0425c5ede6 100644 --- a/homeassistant/components/group/translations/id.json +++ b/homeassistant/components/group/translations/id.json @@ -5,18 +5,21 @@ "data": { "all": "Semua entitas", "entities": "Anggota", + "hide_members": "Sembunyikan anggota", "name": "Nama" }, "description": "Jika \"semua entitas\" diaktifkan, status grup akan menyala jika semua anggota nyala. Jika \"semua entitas\" dinonaktifkan, status grup akan menyala jika ada salah satu atau lebih anggota yang menyala.", - "title": "Grup Baru" + "title": "Tambahkan Grup" }, "cover": { "data": { "entities": "Anggota", + "hide_members": "Sembunyikan anggota", "name": "Nama", - "title": "Grup Baru" + "title": "Tambahkan Grup" }, - "description": "Pilih opsi grup" + "description": "Pilih opsi grup", + "title": "Tambahkan Grup" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Anggota", + "hide_members": "Sembunyikan anggota", "name": "Nama", - "title": "Grup Baru" + "title": "Tambahkan Grup" }, - "description": "Pilih opsi grup" + "description": "Pilih opsi grup", + "title": "Tambahkan Grup" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Semua entitas", "entities": "Anggota", + "hide_members": "Sembunyikan anggota", "name": "Nama", - "title": "Grup Baru" + "title": "Tambahkan Grup" }, - "description": "Pilih opsi grup" + "description": "Jika \"semua entitas\" diaktifkan, status grup akan menyala jika semua anggota nyala. Jika \"semua entitas\" dinonaktifkan, status grup akan menyala jika ada salah satu atau lebih anggota yang menyala.", + "title": "Tambahkan Grup" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Pilih opsi grup" }, + "lock": { + "data": { + "entities": "Anggota", + "hide_members": "Sembunyikan anggota", + "name": "Nama" + }, + "title": "Tambahkan Grup" + }, "media_player": { "data": { "entities": "Anggota", + "hide_members": "Sembunyikan anggota", "name": "Nama", - "title": "Grup Baru" + "title": "Tambahkan Grup" }, - "description": "Pilih opsi grup" + "description": "Pilih opsi grup", + "title": "Tambahkan Grup" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Pilih opsi grup" }, + "switch": { + "data": { + "entities": "Anggota", + "hide_members": "Sembunyikan anggota", + "name": "Nama" + }, + "title": "Tambahkan Grup" + }, "user": { "data": { "group_type": "Jenis grup" }, - "title": "Grup Baru" + "description": "Grup memungkinkan Anda membuat entitas baru yang mewakili beberapa entitas dari jenis yang sama.", + "menu_options": { + "binary_sensor": "Grup sensor biner", + "cover": "Grup penutup", + "fan": "Grup kipas", + "light": "Grup lampu", + "lock": "Grup kunci", + "media_player": "Grup pemutar media", + "switch": "Ganti sakelar" + }, + "title": "Tambahkan Grup" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Semua entitas", + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + }, + "description": "Jika \"semua entitas\" diaktifkan, status grup akan menyala jika semua anggota nyala. Jika \"semua entitas\" dinonaktifkan, status grup akan menyala jika ada salah satu atau lebih anggota yang menyala." + }, "binary_sensor_options": { "data": { "all": "Semua entitas", "entities": "Anggota" } }, + "cover": { + "data": { + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + } + }, "cover_options": { "data": { "entities": "Anggota" } }, + "fan": { + "data": { + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + } + }, "fan_options": { "data": { "entities": "Anggota" } }, + "light": { + "data": { + "all": "Semua entitas", + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + }, + "description": "Jika \"semua entitas\" diaktifkan, status grup akan menyala jika semua anggota nyala. Jika \"semua entitas\" dinonaktifkan, status grup akan menyala jika ada salah satu atau lebih anggota yang menyala." + }, "light_options": { "data": { + "all": "Semua entitas", "entities": "Anggota" } }, + "lock": { + "data": { + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + } + }, + "media_player": { + "data": { + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + } + }, "media_player_options": { "data": { "entities": "Anggota" } + }, + "switch": { + "data": { + "all": "Semua entitas", + "entities": "Anggota", + "hide_members": "Sembunyikan anggota" + }, + "description": "Jika \"semua entitas\" diaktifkan, status grup akan menyala jika semua anggota nyala. Jika \"semua entitas\" dinonaktifkan, status grup akan menyala jika ada salah satu atau lebih anggota yang menyala." } } }, diff --git a/homeassistant/components/group/translations/it.json b/homeassistant/components/group/translations/it.json index 6dc9ff2cce8..4cbac9145d8 100644 --- a/homeassistant/components/group/translations/it.json +++ b/homeassistant/components/group/translations/it.json @@ -5,18 +5,21 @@ "data": { "all": "Tutte le entit\u00e0", "entities": "Membri", + "hide_members": "Nascondi membri", "name": "Nome" }, "description": "Se \"tutte le entit\u00e0\" \u00e8 abilitata, lo stato del gruppo \u00e8 attivo solo se tutti i membri sono attivi. Se \"tutte le entit\u00e0\" \u00e8 disabilitata, lo stato del gruppo \u00e8 attivo se un membro \u00e8 attivo.", - "title": "Nuovo gruppo" + "title": "Aggiungi gruppo" }, "cover": { "data": { "entities": "Membri", + "hide_members": "Nascondi membri", "name": "Nome", - "title": "Nuovo gruppo" + "title": "Aggiungi gruppo" }, - "description": "Seleziona le opzioni di gruppo" + "description": "Seleziona le opzioni di gruppo", + "title": "Aggiungi gruppo" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Membri", + "hide_members": "Nascondi membri", "name": "Nome", - "title": "Nuovo gruppo" + "title": "Aggiungi gruppo" }, - "description": "Seleziona le opzioni di gruppo" + "description": "Seleziona le opzioni di gruppo", + "title": "Aggiungi gruppo" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Tutte le entit\u00e0", "entities": "Membri", + "hide_members": "Nascondi membri", "name": "Nome", - "title": "Nuovo gruppo" + "title": "Aggiungi gruppo" }, - "description": "Seleziona le opzioni di gruppo" + "description": "Se \"tutte le entit\u00e0\" \u00e8 abilitata, lo stato del gruppo \u00e8 attivo solo se tutti i membri sono attivi. Se \"tutte le entit\u00e0\" \u00e8 disabilitata, lo stato del gruppo \u00e8 attivo se un membro \u00e8 attivo.", + "title": "Aggiungi gruppo" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Seleziona le opzioni di gruppo" }, + "lock": { + "data": { + "entities": "Membri", + "hide_members": "Nascondi membri", + "name": "Nome" + }, + "title": "Aggiungi gruppo" + }, "media_player": { "data": { "entities": "Membri", + "hide_members": "Nascondi membri", "name": "Nome", - "title": "Nuovo gruppo" + "title": "Aggiungi gruppo" }, - "description": "Seleziona le opzioni di gruppo" + "description": "Seleziona le opzioni di gruppo", + "title": "Aggiungi gruppo" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Seleziona le opzioni di gruppo" }, + "switch": { + "data": { + "entities": "Membri", + "hide_members": "Nascondi membri", + "name": "Nome" + }, + "title": "Aggiungi gruppo" + }, "user": { "data": { "group_type": "Tipo di gruppo" }, - "title": "Nuovo gruppo" + "description": "I gruppi consentono di creare una nuova entit\u00e0 che rappresenta pi\u00f9 entit\u00e0 dello stesso tipo.", + "menu_options": { + "binary_sensor": "Gruppo di sensori binari", + "cover": "Gruppo di coperture", + "fan": "Gruppo di ventole", + "light": "Gruppo di luci", + "lock": "Blocca gruppo", + "media_player": "Gruppo di lettori multimediali", + "switch": "Gruppo di interruttori" + }, + "title": "Aggiungi gruppo" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Tutte le entit\u00e0", + "entities": "Membri", + "hide_members": "Nascondi membri" + }, + "description": "Se \"tutte le entit\u00e0\" \u00e8 abilitata, lo stato del gruppo \u00e8 attivo solo se tutti i membri sono attivi. Se \"tutte le entit\u00e0\" \u00e8 disabilitata, lo stato del gruppo \u00e8 attivo se un membro \u00e8 attivo." + }, "binary_sensor_options": { "data": { "all": "Tutte le entit\u00e0", "entities": "Membri" } }, + "cover": { + "data": { + "entities": "Membri", + "hide_members": "Nascondi membri" + } + }, "cover_options": { "data": { "entities": "Membri" } }, + "fan": { + "data": { + "entities": "Membri", + "hide_members": "Nascondi membri" + } + }, "fan_options": { "data": { "entities": "Membri" } }, + "light": { + "data": { + "all": "Tutte le entit\u00e0", + "entities": "Membri", + "hide_members": "Nascondi membri" + }, + "description": "Se \"tutte le entit\u00e0\" \u00e8 abilitata, lo stato del gruppo \u00e8 attivo solo se tutti i membri sono attivi. Se \"tutte le entit\u00e0\" \u00e8 disabilitata, lo stato del gruppo \u00e8 attivo se un membro \u00e8 attivo." + }, "light_options": { "data": { + "all": "Tutte le entit\u00e0", "entities": "Membri" } }, + "lock": { + "data": { + "entities": "Membri", + "hide_members": "Nascondi membri" + } + }, + "media_player": { + "data": { + "entities": "Membri", + "hide_members": "Nascondi membri" + } + }, "media_player_options": { "data": { "entities": "Membri" } + }, + "switch": { + "data": { + "all": "Tutte le entit\u00e0", + "entities": "Membri", + "hide_members": "Nascondi membri" + }, + "description": "Se \"tutte le entit\u00e0\" \u00e8 abilitata, lo stato del gruppo \u00e8 attivo solo se tutti i membri sono attivi. Se \"tutte le entit\u00e0\" \u00e8 disabilitata, lo stato del gruppo \u00e8 attivo se un membro \u00e8 attivo." } } }, diff --git a/homeassistant/components/group/translations/ja.json b/homeassistant/components/group/translations/ja.json index dac7583169d..55e414927a4 100644 --- a/homeassistant/components/group/translations/ja.json +++ b/homeassistant/components/group/translations/ja.json @@ -5,6 +5,7 @@ "data": { "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", "name": "\u540d\u524d" }, "description": "\"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u3001\u3059\u3079\u3066\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u306e\u5834\u5408\u306b\u306e\u307f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002 \"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u3044\u305a\u308c\u304b\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u3067\u3042\u308c\u3070\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002", @@ -13,10 +14,12 @@ "cover": { "data": { "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", "name": "\u30b0\u30eb\u30fc\u30d7\u540d", "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" + "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e", + "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", "name": "\u30b0\u30eb\u30fc\u30d7\u540d", "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" + "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e", + "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", "name": "\u30b0\u30eb\u30fc\u30d7\u540d", "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" + "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e", + "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" }, + "lock": { + "data": { + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", + "name": "\u540d\u524d" + }, + "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" + }, "media_player": { "data": { "entities": "\u30b0\u30eb\u30fc\u30d7\u306e\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", "name": "\u30b0\u30eb\u30fc\u30d7\u540d", "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, - "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" + "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e", + "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e" }, + "switch": { + "data": { + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b", + "name": "\u540d\u524d" + }, + "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" + }, "user": { "data": { "group_type": "\u30b0\u30eb\u30fc\u30d7\u30bf\u30a4\u30d7" }, + "description": "\u30b0\u30eb\u30fc\u30d7\u306e\u7a2e\u985e\u3092\u9078\u629e", + "menu_options": { + "binary_sensor": "\u30d0\u30a4\u30ca\u30ea\u30fc\u30bb\u30f3\u30b5\u30fc\u30b0\u30eb\u30fc\u30d7", + "cover": "\u30ab\u30d0\u30fc\u30b0\u30eb\u30fc\u30d7", + "fan": "\u30d5\u30a1\u30f3\u30b0\u30eb\u30fc\u30d7", + "light": "\u30e9\u30a4\u30c8(Light)\u30b0\u30eb\u30fc\u30d7", + "lock": "\u30ed\u30c3\u30af\u30b0\u30eb\u30fc\u30d7", + "media_player": "\u30e1\u30c7\u30a3\u30a2\u30d7\u30ec\u30fc\u30e4\u30fc\u30b0\u30eb\u30fc\u30d7", + "switch": "\u30b9\u30a4\u30c3\u30c1\u30b0\u30eb\u30fc\u30d7" + }, "title": "\u65b0\u3057\u3044\u30b0\u30eb\u30fc\u30d7" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + }, + "description": "\"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u3001\u3059\u3079\u3066\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u306e\u5834\u5408\u306b\u306e\u307f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002 \"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u3044\u305a\u308c\u304b\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u3067\u3042\u308c\u3070\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002" + }, "binary_sensor_options": { "data": { "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", "entities": "\u30e1\u30f3\u30d0\u30fc" } }, + "cover": { + "data": { + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + } + }, "cover_options": { "data": { "entities": "\u30e1\u30f3\u30d0\u30fc" } }, + "fan": { + "data": { + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + } + }, "fan_options": { "data": { "entities": "\u30e1\u30f3\u30d0\u30fc" } }, + "light": { + "data": { + "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + }, + "description": "\"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u3001\u3059\u3079\u3066\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u306e\u5834\u5408\u306b\u306e\u307f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002 \"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u3044\u305a\u308c\u304b\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u3067\u3042\u308c\u3070\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002" + }, "light_options": { "data": { + "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", "entities": "\u30e1\u30f3\u30d0\u30fc" } }, + "lock": { + "data": { + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + } + }, + "media_player": { + "data": { + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + } + }, "media_player_options": { "data": { "entities": "\u30e1\u30f3\u30d0\u30fc" } + }, + "switch": { + "data": { + "all": "\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "entities": "\u30e1\u30f3\u30d0\u30fc", + "hide_members": "\u30e1\u30f3\u30d0\u30fc\u3092\u975e\u8868\u793a\u306b\u3059\u308b" + }, + "description": "\"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u3001\u3059\u3079\u3066\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u306e\u5834\u5408\u306b\u306e\u307f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002 \"\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\" \u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u308b\u5834\u5408\u3001\u3044\u305a\u308c\u304b\u306e\u30e1\u30f3\u30d0\u30fc\u304c\u30aa\u30f3\u3067\u3042\u308c\u3070\u3001\u30b0\u30eb\u30fc\u30d7\u306e\u72b6\u614b\u306f\u30aa\u30f3\u306b\u306a\u308a\u307e\u3059\u3002" } } }, diff --git a/homeassistant/components/group/translations/nl.json b/homeassistant/components/group/translations/nl.json index c636c56f744..e670e2e853a 100644 --- a/homeassistant/components/group/translations/nl.json +++ b/homeassistant/components/group/translations/nl.json @@ -5,18 +5,21 @@ "data": { "all": "Alle entiteiten", "entities": "Leden", + "hide_members": "Verberg leden", "name": "Naam" }, "description": "Als \"alle entiteiten\" is ingeschakeld, is de groep alleen ingeschakeld als alle leden zijn ingeschakeld. Als \"all entities\" is uitgeschakeld, is de groep ingeschakeld als een lid is ingeschakeld.", - "title": "Nieuwe groep" + "title": "Groep toevoegen" }, "cover": { "data": { "entities": "Leden", + "hide_members": "Verberg leden", "name": "Naam", - "title": "Nieuwe groep" + "title": "Groep toevoegen" }, - "description": "Selecteer groepsopties" + "description": "Selecteer groepsopties", + "title": "Groep toevoegen" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Leden", + "hide_members": "Verberg leden", "name": "Naam", - "title": "Nieuwe groep" + "title": "Groep toevoegen" }, - "description": "Selecteer groepsopties" + "description": "Selecteer groepsopties", + "title": "Groep toevoegen" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Alle entiteiten", "entities": "Leden", + "hide_members": "Verberg leden", "name": "Naam", - "title": "Nieuwe groep" + "title": "Groep toevoegen" }, - "description": "Selecteer groepsopties" + "description": "Als \"alle entiteiten\" is ingeschakeld, is de groep alleen ingeschakeld als alle leden zijn ingeschakeld. Als \"all entities\" is uitgeschakeld, is de groep ingeschakeld als een lid is ingeschakeld.", + "title": "Groep toevoegen" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Selecteer groepsopties" }, + "lock": { + "data": { + "entities": "Leden", + "hide_members": "Verberg leden", + "name": "Naam" + }, + "title": "Groep toevoegen" + }, "media_player": { "data": { "entities": "Leden", + "hide_members": "Verberg leden", "name": "Naam", - "title": "Nieuwe groep" + "title": "Groep toevoegen" }, - "description": "Selecteer groepsopties" + "description": "Selecteer groepsopties", + "title": "Groep toevoegen" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Selecteer groepsopties" }, + "switch": { + "data": { + "entities": "Leden", + "hide_members": "Verberg leden", + "name": "Naam" + }, + "title": "Nieuwe groep" + }, "user": { "data": { "group_type": "Groepstype" }, - "title": "Nieuwe groep" + "description": "Met groepen kunt u een nieuwe entiteit cre\u00ebren die meerdere entiteiten van hetzelfde type vertegenwoordigt.", + "menu_options": { + "binary_sensor": "Binaire sensorgroep", + "cover": "Rolluikgroep", + "fan": "Ventilatorgroep", + "light": "Lichtgroep", + "lock": "Groep vergrendelen", + "media_player": "Mediaspelergroep", + "switch": "Groep wisselen" + }, + "title": "Groep toevoegen" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Alle entiteiten", + "entities": "Leden", + "hide_members": "Verberg leden" + }, + "description": "Als \"alle entiteiten\" is ingeschakeld, is de groep alleen ingeschakeld als alle leden zijn ingeschakeld. Als \"all entities\" is uitgeschakeld, is de groep ingeschakeld als een lid is ingeschakeld." + }, "binary_sensor_options": { "data": { "all": "Alle entiteiten", "entities": "Leden" } }, + "cover": { + "data": { + "entities": "Leden", + "hide_members": "Verberg leden" + } + }, "cover_options": { "data": { "entities": "Leden" } }, + "fan": { + "data": { + "entities": "Leden", + "hide_members": "Verberg leden" + } + }, "fan_options": { "data": { "entities": "Leden" } }, + "light": { + "data": { + "all": "Alle entiteiten", + "entities": "Leden", + "hide_members": "Verberg leden" + }, + "description": "Als \"alle entiteiten\" is ingeschakeld, is de groep alleen ingeschakeld als alle leden zijn ingeschakeld. Als \"all entities\" is uitgeschakeld, is de groep ingeschakeld als een lid is ingeschakeld." + }, "light_options": { "data": { + "all": "Alle entiteiten", "entities": "Leden" } }, + "lock": { + "data": { + "entities": "Leden", + "hide_members": "Verberg leden" + } + }, + "media_player": { + "data": { + "entities": "Leden", + "hide_members": "Verberg leden" + } + }, "media_player_options": { "data": { "entities": "Leden" } + }, + "switch": { + "data": { + "all": "Alle entiteiten", + "entities": "Leden", + "hide_members": "Verberg leden" + }, + "description": "Als \"alle entitieiten\" is ingeschakeld, is de status van de groep alleen ingeschakeld als alle leden zijn ingeschakeld. Als \" alle entititeiten\" is uitgeschakeld, is de status van de groep ingeschakeld als een lid is ingeschakeld." } } }, diff --git a/homeassistant/components/group/translations/no.json b/homeassistant/components/group/translations/no.json index 0046479d686..016a9c8e934 100644 --- a/homeassistant/components/group/translations/no.json +++ b/homeassistant/components/group/translations/no.json @@ -5,18 +5,21 @@ "data": { "all": "Alle enheter", "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", "name": "Navn" }, "description": "Hvis \"alle enheter\" er aktivert, er gruppens tilstand p\u00e5 bare hvis alle medlemmene er p\u00e5. Hvis \"alle enheter\" er deaktivert, er gruppens tilstand p\u00e5 hvis et medlem er p\u00e5.", - "title": "Ny gruppe" + "title": "Legg til gruppe" }, "cover": { "data": { "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", "name": "Navn", - "title": "Ny gruppe" + "title": "Legg til gruppe" }, - "description": "Velg gruppealternativer" + "description": "Velg gruppealternativer", + "title": "Legg til gruppe" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", "name": "Navn", - "title": "Ny gruppe" + "title": "Legg til gruppe" }, - "description": "Velg gruppealternativer" + "description": "Velg gruppealternativer", + "title": "Legg til gruppe" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Alle enheter", "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", "name": "Navn", - "title": "Ny gruppe" + "title": "Legg til gruppe" }, - "description": "Velg gruppealternativer" + "description": "Hvis \"alle enheter\" er aktivert, er gruppens tilstand p\u00e5 bare hvis alle medlemmene er p\u00e5. Hvis \"alle enheter\" er deaktivert, er gruppens tilstand p\u00e5 hvis et medlem er p\u00e5.", + "title": "Legg til gruppe" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Velg gruppealternativer" }, + "lock": { + "data": { + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", + "name": "Navn" + }, + "title": "Legg til gruppe" + }, "media_player": { "data": { "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", "name": "Navn", - "title": "Ny gruppe" + "title": "Legg til gruppe" }, - "description": "Velg gruppealternativer" + "description": "Velg gruppealternativer", + "title": "Legg til gruppe" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Velg gruppealternativer" }, + "switch": { + "data": { + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer", + "name": "Navn" + }, + "title": "Legg til gruppe" + }, "user": { "data": { "group_type": "Gruppetype" }, - "title": "Ny gruppe" + "description": "Grupper lar deg opprette en ny enhet som representerer flere enheter av samme type.", + "menu_options": { + "binary_sensor": "Bin\u00e6r sensorgruppe", + "cover": "Dekkgruppe", + "fan": "Viftegruppe", + "light": "Lys-gruppen", + "lock": "L\u00e5s gruppe", + "media_player": "Mediespillergruppe", + "switch": "Bytt gruppe" + }, + "title": "Legg til gruppe" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Alle enheter", + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + }, + "description": "Hvis \"alle enheter\" er aktivert, er gruppens tilstand p\u00e5 bare hvis alle medlemmene er p\u00e5. Hvis \"alle enheter\" er deaktivert, er gruppens tilstand p\u00e5 hvis et medlem er p\u00e5." + }, "binary_sensor_options": { "data": { "all": "Alle enheter", "entities": "Medlemmer" } }, + "cover": { + "data": { + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + } + }, "cover_options": { "data": { "entities": "Medlemmer" } }, + "fan": { + "data": { + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + } + }, "fan_options": { "data": { "entities": "Medlemmer" } }, + "light": { + "data": { + "all": "Alle enheter", + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + }, + "description": "Hvis \"alle enheter\" er aktivert, er gruppens tilstand p\u00e5 bare hvis alle medlemmene er p\u00e5. Hvis \"alle enheter\" er deaktivert, er gruppens tilstand p\u00e5 hvis et medlem er p\u00e5." + }, "light_options": { "data": { + "all": "Alle enheter", "entities": "Medlemmer" } }, + "lock": { + "data": { + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + } + }, + "media_player": { + "data": { + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + } + }, "media_player_options": { "data": { "entities": "Medlemmer" } + }, + "switch": { + "data": { + "all": "Alle enheter", + "entities": "Medlemmer", + "hide_members": "Skjul medlemmer" + }, + "description": "Hvis \"alle enheter\" er aktivert, er gruppens tilstand p\u00e5 bare hvis alle medlemmene er p\u00e5. Hvis \"alle enheter\" er deaktivert, er gruppens tilstand p\u00e5 hvis et medlem er p\u00e5." } } }, diff --git a/homeassistant/components/group/translations/pl.json b/homeassistant/components/group/translations/pl.json index 08595dd8a59..84cbd9f7b4d 100644 --- a/homeassistant/components/group/translations/pl.json +++ b/homeassistant/components/group/translations/pl.json @@ -5,18 +5,21 @@ "data": { "all": "Wszystkie encje", "entities": "Encje", + "hide_members": "Ukryj encje", "name": "Nazwa" }, - "description": "Je\u015bli \u201ewszystkie encje\u201d jest w\u0142\u0105czone, stan grupy jest w\u0142\u0105czony tylko wtedy, gdy wszystkie encje s\u0105 w\u0142\u0105czone. Je\u015bli \u201ewszystkie encje\u201d s\u0105 wy\u0142\u0105czone, stan grupy jest w\u0142\u0105czony, je\u015bli kt\u00f3rakolwiek encja jest w\u0142\u0105czona.", - "title": "Nowa grupa" + "description": "Je\u015bli \u201ewszystkie encje\u201d jest w\u0142\u0105czone, stan grupy jest w\u0142\u0105czony tylko wtedy, gdy wszystkie encje s\u0105 w\u0142\u0105czone. Je\u015bli \u201ewszystkie encje\u201d jest wy\u0142\u0105czone, stan grupy jest w\u0142\u0105czony, je\u015bli kt\u00f3rakolwiek encja jest w\u0142\u0105czona.", + "title": "Dodaj grup\u0119" }, "cover": { "data": { "entities": "Encje", + "hide_members": "Ukryj encje", "name": "Nazwa", - "title": "Nowa grupa" + "title": "Dodaj grup\u0119" }, - "description": "Wybierz opcje grupy" + "description": "Wybierz opcje grupy", + "title": "Dodaj grup\u0119" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Encje", + "hide_members": "Ukryj encje", "name": "Nazwa", - "title": "Nowa grupa" + "title": "Dodaj grup\u0119" }, - "description": "Wybierz opcje grupy" + "description": "Wybierz opcje grupy", + "title": "Dodaj grup\u0119" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Wszystkie encje", "entities": "Encje", + "hide_members": "Ukryj encje", "name": "Nazwa", - "title": "Nowa grupa" + "title": "Dodaj grup\u0119" }, - "description": "Wybierz opcje grupy" + "description": "Je\u015bli \u201ewszystkie encje\u201d jest w\u0142\u0105czone, stan grupy jest w\u0142\u0105czony tylko wtedy, gdy wszystkie encje s\u0105 w\u0142\u0105czone. Je\u015bli \u201ewszystkie encje\u201d jest wy\u0142\u0105czone, stan grupy jest w\u0142\u0105czony, je\u015bli kt\u00f3rakolwiek encja jest w\u0142\u0105czona.", + "title": "Dodaj grup\u0119" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Wybierz opcje grupy" }, + "lock": { + "data": { + "entities": "Encje", + "hide_members": "Ukryj encje", + "name": "Nazwa" + }, + "title": "Dodaj grup\u0119" + }, "media_player": { "data": { "entities": "Encje", + "hide_members": "Ukryj encje", "name": "Nazwa", - "title": "Nowa grupa" + "title": "Dodaj grup\u0119" }, - "description": "Wybierz opcje grupy" + "description": "Wybierz opcje grupy", + "title": "Dodaj grup\u0119" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Wybierz opcje grupy" }, + "switch": { + "data": { + "entities": "Encje", + "hide_members": "Ukryj encje", + "name": "Nazwa" + }, + "title": "Dodaj grup\u0119" + }, "user": { "data": { "group_type": "Rodzaj grupy" }, - "title": "Nowa grupa" + "description": "Grupy umo\u017cliwiaj\u0105 tworzenie nowej encji, kt\u00f3ra reprezentuje wiele encji tego samego typu.", + "menu_options": { + "binary_sensor": "Grupa sensor\u00f3w binarnych", + "cover": "Grupa rolet", + "fan": "Grupa wentylator\u00f3w", + "light": "Grupa \u015bwiate\u0142", + "lock": "Grupa zamk\u00f3w", + "media_player": "Grupa odtwarzaczy multimedialnych", + "switch": "Grupa prze\u0142\u0105cznik\u00f3w" + }, + "title": "Dodaj grup\u0119" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Wszystkie encje", + "entities": "Encje", + "hide_members": "Ukryj encje" + }, + "description": "Je\u015bli \u201ewszystkie encje\u201d jest w\u0142\u0105czone, stan grupy jest w\u0142\u0105czony tylko wtedy, gdy wszystkie encje s\u0105 w\u0142\u0105czone. Je\u015bli \u201ewszystkie encje\u201d jest wy\u0142\u0105czone, stan grupy jest w\u0142\u0105czony, je\u015bli kt\u00f3rakolwiek encja jest w\u0142\u0105czona." + }, "binary_sensor_options": { "data": { "all": "Wszystkie encje", "entities": "Encje" } }, + "cover": { + "data": { + "entities": "Encje", + "hide_members": "Ukryj encje" + } + }, "cover_options": { "data": { "entities": "Encje" } }, + "fan": { + "data": { + "entities": "Encje", + "hide_members": "Ukryj encje" + } + }, "fan_options": { "data": { "entities": "Encje" } }, + "light": { + "data": { + "all": "Wszystkie encje", + "entities": "Encje", + "hide_members": "Ukryj encje" + }, + "description": "Je\u015bli \u201ewszystkie encje\u201d jest w\u0142\u0105czone, stan grupy jest w\u0142\u0105czony tylko wtedy, gdy wszystkie encje s\u0105 w\u0142\u0105czone. Je\u015bli \u201ewszystkie encje\u201d jest wy\u0142\u0105czone, stan grupy jest w\u0142\u0105czony, je\u015bli kt\u00f3rakolwiek encja jest w\u0142\u0105czona." + }, "light_options": { "data": { + "all": "Wszystkie encje", "entities": "Encje" } }, + "lock": { + "data": { + "entities": "Encje", + "hide_members": "Ukryj encje" + } + }, + "media_player": { + "data": { + "entities": "Encje", + "hide_members": "Ukryj encje" + } + }, "media_player_options": { "data": { "entities": "Encje" } + }, + "switch": { + "data": { + "all": "Wszystkie encje", + "entities": "Encje", + "hide_members": "Ukryj encje" + }, + "description": "Je\u015bli \u201ewszystkie encje\u201d jest w\u0142\u0105czone, stan grupy jest w\u0142\u0105czony tylko wtedy, gdy wszystkie encje s\u0105 w\u0142\u0105czone. Je\u015bli \u201ewszystkie encje\u201d jest wy\u0142\u0105czone, stan grupy jest w\u0142\u0105czony, je\u015bli kt\u00f3rakolwiek encja jest w\u0142\u0105czona." } } }, diff --git a/homeassistant/components/group/translations/pt-BR.json b/homeassistant/components/group/translations/pt-BR.json index 5959ba66da7..0987b41f4ae 100644 --- a/homeassistant/components/group/translations/pt-BR.json +++ b/homeassistant/components/group/translations/pt-BR.json @@ -5,18 +5,21 @@ "data": { "all": "Todas as entidades", "entities": "Membros", + "hide_members": "Ocultar membros", "name": "Nome" }, "description": "Se \"todas as entidades\" estiver habilitada, o estado do grupo estar\u00e1 ativado somente se todos os membros estiverem ativados. Se \"todas as entidades\" estiver desabilitada, o estado do grupo estar\u00e1 ativado se algum membro estiver ativado.", - "title": "Novo grupo" + "title": "Adicionar grupo" }, "cover": { "data": { "entities": "Membros", + "hide_members": "Ocultar membros", "name": "Nome", - "title": "Novo grupo" + "title": "Adicionar grupo" }, - "description": "Selecione as op\u00e7\u00f5es do grupo" + "description": "Selecione as op\u00e7\u00f5es do grupo", + "title": "Adicionar grupo" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "Membros", + "hide_members": "Ocultar membros", "name": "Nome", - "title": "Novo grupo" + "title": "Adicionar grupo" }, - "description": "Selecione as op\u00e7\u00f5es do grupo" + "description": "Selecione as op\u00e7\u00f5es do grupo", + "title": "Adicionar grupo" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "Todas as entidades", "entities": "Membros", + "hide_members": "Ocultar membros", "name": "Nome", - "title": "Novo grupo" + "title": "Adicionar grupo" }, - "description": "Selecione as op\u00e7\u00f5es do grupo" + "description": "Se \"todas as entidades\" estiver habilitada, o estado do grupo estar\u00e1 ativado somente se todos os membros estiverem ativados. Se \"todas as entidades\" estiver desabilitada, o estado do grupo estar\u00e1 ativado se algum membro estiver ativado.", + "title": "Adicionar grupo" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "Selecione as op\u00e7\u00f5es do grupo" }, + "lock": { + "data": { + "entities": "Membros", + "hide_members": "Ocultar membros", + "name": "Nome" + }, + "title": "Adicionar grupo" + }, "media_player": { "data": { "entities": "Membros", + "hide_members": "Ocultar membros", "name": "Nome", - "title": "Novo grupo" + "title": "Adicionar grupo" }, - "description": "Selecione as op\u00e7\u00f5es do grupo" + "description": "Selecione as op\u00e7\u00f5es do grupo", + "title": "Adicionar grupo" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "Selecione as op\u00e7\u00f5es do grupo" }, + "switch": { + "data": { + "entities": "Membros", + "hide_members": "Ocultar membros", + "name": "Nome" + }, + "title": "Adicionar grupo" + }, "user": { "data": { "group_type": "Tipo de grupo" }, - "title": "Novo grupo" + "description": "Os grupos permitem que voc\u00ea crie uma nova entidade que representa v\u00e1rias entidades do mesmo tipo.", + "menu_options": { + "binary_sensor": "Grupo de sensores bin\u00e1rios", + "cover": "Grupo de persianas", + "fan": "Grupo de ventiladores", + "light": "Grupo de l\u00e2mpadas", + "lock": "Grupo de fechaduras", + "media_player": "Grupo de reprodutores de m\u00eddia", + "switch": "Grupo de interruptores" + }, + "title": "Adicionar grupo" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "Todas as entidades", + "entities": "Membros", + "hide_members": "Ocultar membros" + }, + "description": "Se \"todas as entidades\" estiver habilitada, o estado do grupo estar\u00e1 ativado somente se todos os membros estiverem ativados. Se \"todas as entidades\" estiver desabilitada, o estado do grupo estar\u00e1 ativado se algum membro estiver ativado." + }, "binary_sensor_options": { "data": { "all": "Todas as entidades", "entities": "Membros" } }, + "cover": { + "data": { + "entities": "Membros", + "hide_members": "Ocultar membros" + } + }, "cover_options": { "data": { "entities": "Membros" } }, + "fan": { + "data": { + "entities": "Membros", + "hide_members": "Ocultar membros" + } + }, "fan_options": { "data": { "entities": "Membros" } }, + "light": { + "data": { + "all": "Todas as entidades", + "entities": "Membros", + "hide_members": "Ocultar membros" + }, + "description": "Se \"todas as entidades\" estiver habilitada, o estado do grupo estar\u00e1 ativado somente se todos os membros estiverem ativados. Se \"todas as entidades\" estiver desabilitada, o estado do grupo estar\u00e1 ativado se algum membro estiver ativado." + }, "light_options": { "data": { + "all": "Todas as entidades", "entities": "Membros" } }, + "lock": { + "data": { + "entities": "Membros", + "hide_members": "Ocultar membros" + } + }, + "media_player": { + "data": { + "entities": "Membros", + "hide_members": "Ocultar membros" + } + }, "media_player_options": { "data": { "entities": "Membros" } + }, + "switch": { + "data": { + "all": "Todas as entidades", + "entities": "Membros", + "hide_members": "Ocultar membros" + }, + "description": "Se \"todas as entidades\" estiver habilitada, o estado do grupo estar\u00e1 ativado somente se todos os membros estiverem ativados. Se \"todas as entidades\" estiver desabilitada, o estado do grupo estar\u00e1 ativado se algum membro estiver ativado." } } }, diff --git a/homeassistant/components/group/translations/ru.json b/homeassistant/components/group/translations/ru.json index baba258b654..0ba6d79900e 100644 --- a/homeassistant/components/group/translations/ru.json +++ b/homeassistant/components/group/translations/ru.json @@ -5,18 +5,21 @@ "data": { "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, "description": "\u0415\u0441\u043b\u0438 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b\", \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u0441\u0435 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432.", - "title": "\u041d\u043e\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430" + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, "cover": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", - "title": "\u041d\u043e\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430" + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b." + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b.", + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", - "title": "\u041d\u043e\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430" + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b." + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b.", + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", - "title": "\u041d\u043e\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430" + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b." + "description": "\u0415\u0441\u043b\u0438 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b\", \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u0441\u0435 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432.", + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b." }, + "lock": { + "data": { + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" + }, "media_player": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", - "title": "\u041d\u043e\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430" + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b." + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b.", + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0433\u0440\u0443\u043f\u043f\u044b." }, + "switch": { + "data": { + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" + }, "user": { "data": { "group_type": "\u0422\u0438\u043f \u0433\u0440\u0443\u043f\u043f\u044b" }, - "title": "\u041d\u043e\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430" + "description": "\u0413\u0440\u0443\u043f\u043f\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430.", + "menu_options": { + "binary_sensor": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0431\u0438\u043d\u0430\u0440\u043d\u044b\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432", + "cover": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0448\u0442\u043e\u0440 \u0438\u043b\u0438 \u0436\u0430\u043b\u044e\u0437\u0438", + "fan": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0442\u043e\u0440\u043e\u0432", + "light": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f", + "lock": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0437\u0430\u043c\u043a\u043e\u0432", + "media_player": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u043e\u0432", + "switch": "\u0413\u0440\u0443\u043f\u043f\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u0435\u0439" + }, + "title": "\u0413\u0440\u0443\u043f\u043f\u0430" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + }, + "description": "\u0415\u0441\u043b\u0438 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b\", \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u0441\u0435 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432." + }, "binary_sensor_options": { "data": { "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438" } }, + "cover": { + "data": { + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + } + }, "cover_options": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438" } }, + "fan": { + "data": { + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + } + }, "fan_options": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438" } }, + "light": { + "data": { + "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + }, + "description": "\u0415\u0441\u043b\u0438 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b\", \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u0441\u0435 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432." + }, "light_options": { "data": { + "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438" } }, + "lock": { + "data": { + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + } + }, + "media_player": { + "data": { + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + } + }, "media_player_options": { "data": { "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438" } + }, + "switch": { + "data": { + "all": "\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", + "entities": "\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438", + "hide_members": "\u0421\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432" + }, + "description": "\u0415\u0441\u043b\u0438 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \"\u0412\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b\", \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u0441\u0435 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\" \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u0435\u0451 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432." } } }, diff --git a/homeassistant/components/group/translations/tr.json b/homeassistant/components/group/translations/tr.json index bbb4600a4ed..354ed29214c 100644 --- a/homeassistant/components/group/translations/tr.json +++ b/homeassistant/components/group/translations/tr.json @@ -1,12 +1,25 @@ { "config": { "step": { + "binary_sensor": { + "data": { + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad" + }, + "description": "\"T\u00fcm varl\u0131klar\" etkinle\u015ftirilirse, grubun durumu yaln\u0131zca t\u00fcm \u00fcyeler a\u00e7\u0131ksa a\u00e7\u0131kt\u0131r. \"T\u00fcm varl\u0131klar\" devre d\u0131\u015f\u0131 b\u0131rak\u0131l\u0131rsa, herhangi bir \u00fcye a\u00e7\u0131ksa grubun durumu a\u00e7\u0131kt\u0131r.", + "title": "Grup Ekle" + }, "cover": { "data": { - "entities": "Grup \u00fcyeleri", - "name": "Grup ismi" + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad", + "title": "Grup Ekle" }, - "description": "Grup se\u00e7eneklerini se\u00e7in" + "description": "Grup se\u00e7eneklerini se\u00e7in", + "title": "Grup Ekle" }, "cover_options": { "data": { @@ -16,10 +29,13 @@ }, "fan": { "data": { - "entities": "Grup \u00fcyeleri", - "name": "Grup ismi" + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad", + "title": "Grup Ekle" }, - "description": "Grup se\u00e7eneklerini se\u00e7in" + "description": "Grup se\u00e7eneklerini se\u00e7in", + "title": "Grup Ekle" }, "fan_options": { "data": { @@ -35,10 +51,14 @@ }, "light": { "data": { - "entities": "Grup \u00fcyeleri", - "name": "Grup ismi" + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad", + "title": "Grup Ekle" }, - "description": "Grup se\u00e7eneklerini se\u00e7in" + "description": "\"T\u00fcm varl\u0131klar\" etkinle\u015ftirilirse, grubun durumu yaln\u0131zca t\u00fcm \u00fcyeler a\u00e7\u0131ksa a\u00e7\u0131kt\u0131r. \"T\u00fcm varl\u0131klar\" devre d\u0131\u015f\u0131 b\u0131rak\u0131l\u0131rsa, herhangi bir \u00fcye a\u00e7\u0131ksa grubun durumu a\u00e7\u0131kt\u0131r.", + "title": "Grup Ekle" }, "light_options": { "data": { @@ -46,18 +66,132 @@ }, "description": "Grup se\u00e7eneklerini se\u00e7in" }, + "lock": { + "data": { + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad" + }, + "title": "Grup Ekle" + }, "media_player": { "data": { - "entities": "Grup \u00fcyeleri", - "name": "Grup ismi" + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad", + "title": "Grup Ekle" }, - "description": "Grup se\u00e7eneklerini se\u00e7in" + "description": "Grup se\u00e7eneklerini se\u00e7in", + "title": "Grup Ekle" }, "media_player_options": { "data": { "entities": "Grup \u00fcyeleri" }, "description": "Grup se\u00e7eneklerini se\u00e7in" + }, + "switch": { + "data": { + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle", + "name": "Ad" + }, + "title": "Grup Ekle" + }, + "user": { + "data": { + "group_type": "Grup t\u00fcr\u00fc" + }, + "description": "Gruplar, ayn\u0131 t\u00fcrden birden \u00e7ok varl\u0131\u011f\u0131 temsil eden yeni bir varl\u0131k olu\u015fturman\u0131za olanak tan\u0131r.", + "menu_options": { + "binary_sensor": "\u0130kili sens\u00f6r grubu", + "cover": "Kepenk grubu", + "fan": "Fan grubu", + "light": "I\u015f\u0131k grubu", + "lock": "Grubu kilitle", + "media_player": "Medya oynat\u0131c\u0131 grubu", + "switch": "Grubu de\u011fi\u015ftir" + }, + "title": "Grup Ekle" + } + } + }, + "options": { + "step": { + "binary_sensor": { + "data": { + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + }, + "description": "\"T\u00fcm varl\u0131klar\" etkinle\u015ftirilirse, grubun durumu yaln\u0131zca t\u00fcm \u00fcyeler a\u00e7\u0131ksa a\u00e7\u0131kt\u0131r. \"T\u00fcm varl\u0131klar\" devre d\u0131\u015f\u0131 b\u0131rak\u0131l\u0131rsa, herhangi bir \u00fcye a\u00e7\u0131ksa grubun durumu a\u00e7\u0131kt\u0131r." + }, + "binary_sensor_options": { + "data": { + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler" + } + }, + "cover": { + "data": { + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + } + }, + "cover_options": { + "data": { + "entities": "\u00dcyeler" + } + }, + "fan": { + "data": { + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + } + }, + "fan_options": { + "data": { + "entities": "\u00dcyeler" + } + }, + "light": { + "data": { + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + }, + "description": "\"T\u00fcm varl\u0131klar\" etkinle\u015ftirilirse, grubun durumu yaln\u0131zca t\u00fcm \u00fcyeler a\u00e7\u0131ksa a\u00e7\u0131kt\u0131r. \"T\u00fcm varl\u0131klar\" devre d\u0131\u015f\u0131 b\u0131rak\u0131l\u0131rsa, herhangi bir \u00fcye a\u00e7\u0131ksa grubun durumu a\u00e7\u0131kt\u0131r." + }, + "light_options": { + "data": { + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler" + } + }, + "lock": { + "data": { + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + } + }, + "media_player": { + "data": { + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + } + }, + "media_player_options": { + "data": { + "entities": "\u00dcyeler" + } + }, + "switch": { + "data": { + "all": "T\u00fcm varl\u0131klar", + "entities": "\u00dcyeler", + "hide_members": "\u00dcyeleri gizle" + }, + "description": "\"T\u00fcm varl\u0131klar\" etkinle\u015ftirilirse, grubun durumu yaln\u0131zca t\u00fcm \u00fcyeler a\u00e7\u0131ksa a\u00e7\u0131kt\u0131r. \"T\u00fcm varl\u0131klar\" devre d\u0131\u015f\u0131 b\u0131rak\u0131l\u0131rsa, herhangi bir \u00fcye a\u00e7\u0131ksa grubun durumu a\u00e7\u0131kt\u0131r." } } }, diff --git a/homeassistant/components/group/translations/zh-Hant.json b/homeassistant/components/group/translations/zh-Hant.json index 9187455ad17..380c2216976 100644 --- a/homeassistant/components/group/translations/zh-Hant.json +++ b/homeassistant/components/group/translations/zh-Hant.json @@ -5,6 +5,7 @@ "data": { "all": "\u6240\u6709\u5be6\u9ad4", "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", "name": "\u540d\u7a31" }, "description": "\u5047\u5982\u958b\u555f \"\u6240\u6709\u5be6\u9ad4\"\uff0c\u50c5\u65bc\u7576\u6240\u6709\u6210\u54e1\u90fd\u70ba\u958b\u555f\u6642\u3001\u88dd\u614b\u624d\u6703\u986f\u793a\u70ba\u958b\u555f\u3002\u5047\u5982 \"\u6240\u6709\u5be6\u9ad4\" \u70ba\u95dc\u9589\u3001\u5247\u4efb\u4f55\u6210\u54e1\u958b\u59cb\u6642\uff0c\u7686\u6703\u986f\u793a\u70ba\u958b\u555f\u3002", @@ -13,10 +14,12 @@ "cover": { "data": { "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", "name": "\u540d\u7a31", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" + "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805", + "title": "\u65b0\u589e\u7fa4\u7d44" }, "cover_options": { "data": { @@ -27,10 +30,12 @@ "fan": { "data": { "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", "name": "\u540d\u7a31", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" + "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805", + "title": "\u65b0\u589e\u7fa4\u7d44" }, "fan_options": { "data": { @@ -46,11 +51,14 @@ }, "light": { "data": { + "all": "\u6240\u6709\u5be6\u9ad4", "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", "name": "\u540d\u7a31", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" + "description": "\u5047\u5982\u958b\u555f \"\u6240\u6709\u5be6\u9ad4\"\uff0c\u50c5\u65bc\u7576\u6240\u6709\u6210\u54e1\u90fd\u70ba\u958b\u555f\u6642\u3001\u88dd\u614b\u624d\u6703\u986f\u793a\u70ba\u958b\u555f\u3002\u5047\u5982 \"\u6240\u6709\u5be6\u9ad4\" \u70ba\u95dc\u9589\u3001\u5247\u4efb\u4f55\u6210\u54e1\u958b\u59cb\u6642\uff0c\u7686\u6703\u986f\u793a\u70ba\u958b\u555f\u3002", + "title": "\u65b0\u589e\u7fa4\u7d44" }, "light_options": { "data": { @@ -58,13 +66,23 @@ }, "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" }, + "lock": { + "data": { + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", + "name": "\u540d\u7a31" + }, + "title": "\u65b0\u589e\u7fa4\u7d44" + }, "media_player": { "data": { "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", "name": "\u540d\u7a31", "title": "\u65b0\u589e\u7fa4\u7d44" }, - "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" + "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805", + "title": "\u65b0\u589e\u7fa4\u7d44" }, "media_player_options": { "data": { @@ -72,41 +90,108 @@ }, "description": "\u9078\u64c7\u7fa4\u7d44\u9078\u9805" }, + "switch": { + "data": { + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1", + "name": "\u540d\u7a31" + }, + "title": "\u65b0\u589e\u7fa4\u7d44" + }, "user": { "data": { "group_type": "\u7fa4\u7d44\u985e\u5225" }, + "description": "\u7fa4\u7d44\u5141\u8a31\u4f7f\u7528\u76f8\u540c\u985e\u5225\u7684\u591a\u500b\u5be6\u9ad4\u7d44\u6210\u65b0\u5be6\u9ad4\u3002", + "menu_options": { + "binary_sensor": "\u4e8c\u9032\u4f4d\u611f\u6e2c\u5668\u7fa4\u7d44", + "cover": "\u6372\u7c3e\u7fa4\u7d44", + "fan": "\u98a8\u6247\u7fa4\u7d44", + "light": "\u71c8\u5149\u7fa4\u7d44", + "lock": "\u9580\u9396\u7fa4\u7d44", + "media_player": "\u5a92\u9ad4\u64ad\u653e\u5668\u7fa4\u7d44", + "switch": "\u958b\u95dc\u7fa4\u7d44" + }, "title": "\u65b0\u589e\u7fa4\u7d44" } } }, "options": { "step": { + "binary_sensor": { + "data": { + "all": "\u6240\u6709\u5be6\u9ad4", + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + }, + "description": "\u5047\u5982\u958b\u555f \"\u6240\u6709\u5be6\u9ad4\"\uff0c\u50c5\u65bc\u7576\u6240\u6709\u6210\u54e1\u90fd\u70ba\u958b\u555f\u6642\u3001\u88dd\u614b\u624d\u6703\u986f\u793a\u70ba\u958b\u555f\u3002\u5047\u5982 \"\u6240\u6709\u5be6\u9ad4\" \u70ba\u95dc\u9589\u3001\u5247\u4efb\u4f55\u6210\u54e1\u958b\u59cb\u6642\uff0c\u7686\u6703\u986f\u793a\u70ba\u958b\u555f\u3002" + }, "binary_sensor_options": { "data": { "all": "\u6240\u6709\u5be6\u9ad4", "entities": "\u6210\u54e1" } }, + "cover": { + "data": { + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + } + }, "cover_options": { "data": { "entities": "\u6210\u54e1" } }, + "fan": { + "data": { + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + } + }, "fan_options": { "data": { "entities": "\u6210\u54e1" } }, + "light": { + "data": { + "all": "\u6240\u6709\u5be6\u9ad4", + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + }, + "description": "\u5047\u5982\u958b\u555f \"\u6240\u6709\u5be6\u9ad4\"\uff0c\u50c5\u65bc\u7576\u6240\u6709\u6210\u54e1\u90fd\u70ba\u958b\u555f\u6642\u3001\u88dd\u614b\u624d\u6703\u986f\u793a\u70ba\u958b\u555f\u3002\u5047\u5982 \"\u6240\u6709\u5be6\u9ad4\" \u70ba\u95dc\u9589\u3001\u5247\u4efb\u4f55\u6210\u54e1\u958b\u59cb\u6642\uff0c\u7686\u6703\u986f\u793a\u70ba\u958b\u555f\u3002" + }, "light_options": { "data": { + "all": "\u6240\u6709\u5be6\u9ad4", "entities": "\u6210\u54e1" } }, + "lock": { + "data": { + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + } + }, + "media_player": { + "data": { + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + } + }, "media_player_options": { "data": { "entities": "\u6210\u54e1" } + }, + "switch": { + "data": { + "all": "\u6240\u6709\u5be6\u9ad4", + "entities": "\u6210\u54e1", + "hide_members": "\u96b1\u85cf\u6210\u54e1" + }, + "description": "\u5047\u5982\u958b\u555f \"\u6240\u6709\u5be6\u9ad4\"\uff0c\u50c5\u65bc\u7576\u6240\u6709\u6210\u54e1\u90fd\u70ba\u958b\u555f\u6642\u3001\u88dd\u614b\u624d\u6703\u986f\u793a\u70ba\u958b\u555f\u3002\u5047\u5982 \"\u6240\u6709\u5be6\u9ad4\" \u70ba\u95dc\u9589\u3001\u5247\u4efb\u4f55\u6210\u54e1\u958b\u59cb\u6642\uff0c\u7686\u6703\u986f\u793a\u70ba\u958b\u555f\u3002" } } }, diff --git a/homeassistant/components/growatt_server/translations/hu.json b/homeassistant/components/growatt_server/translations/hu.json index 5b2efc737fe..44d87bf753e 100644 --- a/homeassistant/components/growatt_server/translations/hu.json +++ b/homeassistant/components/growatt_server/translations/hu.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "url": "URL", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index 123ea860d3b..d1b0fb056ff 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -6,15 +6,12 @@ import logging from gsp import GstreamerPlayer import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_IDLE from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -27,14 +24,6 @@ CONF_PIPELINE = "pipeline" DOMAIN = "gstreamer" -SUPPORT_GSTREAMER = ( - SUPPORT_VOLUME_SET - | SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_PLAY_MEDIA - | SUPPORT_NEXT_TRACK -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PIPELINE): cv.string} ) @@ -63,6 +52,14 @@ def setup_platform( class GstreamerDevice(MediaPlayerEntity): """Representation of a Gstreamer device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.NEXT_TRACK + ) + def __init__(self, player, name): """Initialize the Gstreamer device.""" self._player = player @@ -128,11 +125,6 @@ class GstreamerDevice(MediaPlayerEntity): """Return the volume level.""" return self._volume - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_GSTREAMER - @property def state(self): """Return the state of the player.""" diff --git a/homeassistant/components/guardian/manifest.json b/homeassistant/components/guardian/manifest.json index 9a663dc104e..fe9a453a166 100644 --- a/homeassistant/components/guardian/manifest.json +++ b/homeassistant/components/guardian/manifest.json @@ -3,7 +3,7 @@ "name": "Elexa Guardian", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/guardian", - "requirements": ["aioguardian==2021.11.0"], + "requirements": ["aioguardian==2022.03.2"], "zeroconf": ["_api._udp.local."], "codeowners": ["@bachya"], "iot_class": "local_polling", diff --git a/homeassistant/components/guardian/translations/hu.json b/homeassistant/components/guardian/translations/hu.json index 04b62bb660e..82fd422abf7 100644 --- a/homeassistant/components/guardian/translations/hu.json +++ b/homeassistant/components/guardian/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { diff --git a/homeassistant/components/hangouts/translations/ca.json b/homeassistant/components/hangouts/translations/ca.json index b4114312f3e..8b629201cc1 100644 --- a/homeassistant/components/hangouts/translations/ca.json +++ b/homeassistant/components/hangouts/translations/ca.json @@ -24,7 +24,7 @@ "password": "Contrasenya" }, "description": "Buit", - "title": "Inici de sessi\u00f3 de Google Hangouts" + "title": "Inici de sessi\u00f3 de Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/de.json b/homeassistant/components/hangouts/translations/de.json index 02481670a09..b26618940be 100644 --- a/homeassistant/components/hangouts/translations/de.json +++ b/homeassistant/components/hangouts/translations/de.json @@ -23,7 +23,7 @@ "password": "Passwort" }, "description": "Leer", - "title": "Google Hangouts Login" + "title": "Google Chat Anmeldung" } } } diff --git a/homeassistant/components/hangouts/translations/et.json b/homeassistant/components/hangouts/translations/et.json index 7d6deb2ef53..96ed8b998ec 100644 --- a/homeassistant/components/hangouts/translations/et.json +++ b/homeassistant/components/hangouts/translations/et.json @@ -24,7 +24,7 @@ "password": "Salas\u00f5na" }, "description": "", - "title": "Google Hangoutsi sisselogimine" + "title": "Google Chat'i sisselogimine" } } } diff --git a/homeassistant/components/hangouts/translations/fr.json b/homeassistant/components/hangouts/translations/fr.json index da674b1c775..78a2517d29a 100644 --- a/homeassistant/components/hangouts/translations/fr.json +++ b/homeassistant/components/hangouts/translations/fr.json @@ -5,9 +5,9 @@ "unknown": "Erreur inattendue" }, "error": { - "invalid_2fa": "Authentification \u00e0 2 facteurs invalide, veuillez r\u00e9essayer.", + "invalid_2fa": "Authentification \u00e0 deux facteurs non valide, veuillez r\u00e9essayer.", "invalid_2fa_method": "M\u00e9thode 2FA non valide (v\u00e9rifiez sur le t\u00e9l\u00e9phone).", - "invalid_login": "Login invalide, veuillez r\u00e9essayer." + "invalid_login": "Identifiant non valide, veuillez r\u00e9essayer." }, "step": { "2fa": { @@ -24,7 +24,7 @@ "password": "Mot de passe" }, "description": "Vide", - "title": "Connexion \u00e0 Google Hangouts" + "title": "Connexion \u00e0 Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/he.json b/homeassistant/components/hangouts/translations/he.json index 9f0e3b48a62..ad696cad365 100644 --- a/homeassistant/components/hangouts/translations/he.json +++ b/homeassistant/components/hangouts/translations/he.json @@ -21,7 +21,7 @@ "email": "\u05d3\u05d5\u05d0\"\u05dc", "password": "\u05e1\u05d9\u05e1\u05de\u05d4" }, - "title": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05dc- Google Hangouts" + "title": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05dc\u05e6'\u05d0\u05d8 \u05e9\u05dc \u05d2\u05d5\u05d2\u05dc" } } } diff --git a/homeassistant/components/hangouts/translations/hu.json b/homeassistant/components/hangouts/translations/hu.json index eda0144a818..1ea997aa098 100644 --- a/homeassistant/components/hangouts/translations/hu.json +++ b/homeassistant/components/hangouts/translations/hu.json @@ -24,7 +24,7 @@ "password": "Jelsz\u00f3" }, "description": "\u00dcres", - "title": "Google Hangouts Bejelentkez\u00e9s" + "title": "Google Chat bejelentkez\u00e9s" } } } diff --git a/homeassistant/components/hangouts/translations/id.json b/homeassistant/components/hangouts/translations/id.json index 39c68dda211..2336b211c9e 100644 --- a/homeassistant/components/hangouts/translations/id.json +++ b/homeassistant/components/hangouts/translations/id.json @@ -24,7 +24,7 @@ "password": "Kata Sandi" }, "description": "Kosong", - "title": "Info Masuk Google Hangouts" + "title": "Info Masuk Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/it.json b/homeassistant/components/hangouts/translations/it.json index 3d9322b1152..76d81a184d9 100644 --- a/homeassistant/components/hangouts/translations/it.json +++ b/homeassistant/components/hangouts/translations/it.json @@ -24,7 +24,7 @@ "password": "Password" }, "description": "Vuoto", - "title": "Accesso a Google Hangouts" + "title": "Accesso a Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/nl.json b/homeassistant/components/hangouts/translations/nl.json index 456d2193922..276c310da5a 100644 --- a/homeassistant/components/hangouts/translations/nl.json +++ b/homeassistant/components/hangouts/translations/nl.json @@ -24,7 +24,7 @@ "password": "Wachtwoord" }, "description": "Leeg", - "title": "Google Hangouts inlog" + "title": "Google Chat-login" } } } diff --git a/homeassistant/components/hangouts/translations/no.json b/homeassistant/components/hangouts/translations/no.json index d4fe4dbb5a6..751d54c852e 100644 --- a/homeassistant/components/hangouts/translations/no.json +++ b/homeassistant/components/hangouts/translations/no.json @@ -24,7 +24,7 @@ "password": "Passord" }, "description": "", - "title": "Google Hangouts p\u00e5logging" + "title": "Google Chat-p\u00e5logging" } } } diff --git a/homeassistant/components/hangouts/translations/pl.json b/homeassistant/components/hangouts/translations/pl.json index 8fb7e9e64d9..4dafdc5d996 100644 --- a/homeassistant/components/hangouts/translations/pl.json +++ b/homeassistant/components/hangouts/translations/pl.json @@ -24,7 +24,7 @@ "password": "Has\u0142o" }, "description": "Pusty", - "title": "Logowanie do Google Hangouts" + "title": "Logowanie do Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/pt-BR.json b/homeassistant/components/hangouts/translations/pt-BR.json index c60d9d8ec47..9e4d04cd989 100644 --- a/homeassistant/components/hangouts/translations/pt-BR.json +++ b/homeassistant/components/hangouts/translations/pt-BR.json @@ -24,7 +24,7 @@ "password": "Senha" }, "description": "Vazio", - "title": "Login do Hangouts do Google" + "title": "Login no Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/ru.json b/homeassistant/components/hangouts/translations/ru.json index d352258ba33..781e1e25eef 100644 --- a/homeassistant/components/hangouts/translations/ru.json +++ b/homeassistant/components/hangouts/translations/ru.json @@ -24,7 +24,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, "description": "\u043f\u0443\u0441\u0442\u043e", - "title": "Google Hangouts" + "title": "Google Chat" } } } diff --git a/homeassistant/components/hangouts/translations/tr.json b/homeassistant/components/hangouts/translations/tr.json index 84fb80abaf5..5ddf2a64cbb 100644 --- a/homeassistant/components/hangouts/translations/tr.json +++ b/homeassistant/components/hangouts/translations/tr.json @@ -24,7 +24,7 @@ "password": "Parola" }, "description": "Bo\u015f", - "title": "Google Hangouts Giri\u015fi" + "title": "Google Sohbet Giri\u015fi" } } } diff --git a/homeassistant/components/hangouts/translations/zh-Hant.json b/homeassistant/components/hangouts/translations/zh-Hant.json index 678aacc5b62..f9884d5e214 100644 --- a/homeassistant/components/hangouts/translations/zh-Hant.json +++ b/homeassistant/components/hangouts/translations/zh-Hant.json @@ -24,7 +24,7 @@ "password": "\u5bc6\u78bc" }, "description": "\u7a7a\u767d", - "title": "\u767b\u5165 Google Hangouts" + "title": "\u767b\u5165 Google Chat" } } } diff --git a/homeassistant/components/harman_kardon_avr/media_player.py b/homeassistant/components/harman_kardon_avr/media_player.py index 1e23418101c..5b0c87cfbc5 100644 --- a/homeassistant/components/harman_kardon_avr/media_player.py +++ b/homeassistant/components/harman_kardon_avr/media_player.py @@ -4,13 +4,10 @@ from __future__ import annotations import hkavr import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant @@ -21,14 +18,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType DEFAULT_NAME = "Harman Kardon AVR" DEFAULT_PORT = 10025 -SUPPORT_HARMAN_KARDON_AVR = ( - SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -58,6 +47,14 @@ def setup_platform( class HkAvrDevice(MediaPlayerEntity): """Representation of a Harman Kardon AVR / JBL AVR TV.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, avr): """Initialize a new HarmanKardonAVR.""" self._avr = avr @@ -109,11 +106,6 @@ class HkAvrDevice(MediaPlayerEntity): """Available sources.""" return self._source_list - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_HARMAN_KARDON_AVR - def turn_on(self): """Turn the AVR on.""" self._avr.power_on() diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 9f677ef0e8d..93d72f0ef7e 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -4,7 +4,6 @@ import logging import voluptuous as vol -from homeassistant.components import remote from homeassistant.components.remote import ( ATTR_ACTIVITY, ATTR_DELAY_SECS, @@ -12,7 +11,8 @@ from homeassistant.components.remote import ( ATTR_HOLD_SECS, ATTR_NUM_REPEATS, DEFAULT_DELAY_SECS, - SUPPORT_ACTIVITY, + RemoteEntity, + RemoteEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -77,9 +77,11 @@ async def async_setup_entry( ) -class HarmonyRemote(HarmonyEntity, remote.RemoteEntity, RestoreEntity): +class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): """Remote representation used to control a Harmony device.""" + _attr_supported_features = RemoteEntityFeature.ACTIVITY + def __init__(self, data, activity, delay_secs, out_path): """Initialize HarmonyRemote class.""" super().__init__(data=data) @@ -94,7 +96,6 @@ class HarmonyRemote(HarmonyEntity, remote.RemoteEntity, RestoreEntity): self._attr_unique_id = data.unique_id self._attr_device_info = self._data.device_info(DOMAIN) self._attr_name = data.name - self._attr_supported_features = SUPPORT_ACTIVITY async def _async_update_options(self, data): """Change options when the options flow does.""" diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 278f8a50ebc..a3689f61746 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -53,6 +53,7 @@ from .const import ( ATTR_ADDONS, ATTR_AUTO_UPDATE, ATTR_CHANGELOG, + ATTR_COMPRESSED, ATTR_DISCOVERY, ATTR_FOLDERS, ATTR_HOMEASSISTANT, @@ -127,7 +128,11 @@ SCHEMA_ADDON_STDIN = SCHEMA_ADDON.extend( ) SCHEMA_BACKUP_FULL = vol.Schema( - {vol.Optional(ATTR_NAME): cv.string, vol.Optional(ATTR_PASSWORD): cv.string} + { + vol.Optional(ATTR_NAME): cv.string, + vol.Optional(ATTR_PASSWORD): cv.string, + vol.Optional(ATTR_COMPRESSED): cv.boolean, + } ) SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend( diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 8c27fdebb17..2d99b1f5605 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -6,6 +6,7 @@ DOMAIN = "hassio" ATTR_ADDON = "addon" ATTR_ADDONS = "addons" ATTR_ADMIN = "admin" +ATTR_COMPRESSED = "compressed" ATTR_CONFIG = "config" ATTR_DATA = "data" ATTR_DISCOVERY = "discovery" diff --git a/homeassistant/components/hassio/diagnostics.py b/homeassistant/components/hassio/diagnostics.py new file mode 100644 index 00000000000..41b3fc1c5c3 --- /dev/null +++ b/homeassistant/components/hassio/diagnostics.py @@ -0,0 +1,52 @@ +"""Diagnostics support for Supervisor.""" +from __future__ import annotations + +from typing import Any + +from attr import asdict + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from . import ADDONS_COORDINATOR, HassioDataUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, + config_entry: ConfigEntry, +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: HassioDataUpdateCoordinator = hass.data[ADDONS_COORDINATOR] + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + + devices = [] + + registry_devices = dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ) + + for device in registry_devices: + entities = [] + + registry_entities = er.async_entries_for_device( + entity_registry, + device_id=device.id, + include_disabled_entities=True, + ) + + for entity_entry in registry_entities: + state_dict = None + if state := hass.states.get(entity_entry.entity_id): + state_dict = dict(state.as_dict()) + state_dict.pop("context", None) + + entities.append({"entry": asdict(entity_entry), "state": state_dict}) + + devices.append({"device": asdict(device), "entities": entities}) + + return { + "coordinator_data": coordinator.data, + "devices": devices, + } diff --git a/homeassistant/components/hassio/services.yaml b/homeassistant/components/hassio/services.yaml index 6186f222183..e526074b1a9 100644 --- a/homeassistant/components/hassio/services.yaml +++ b/homeassistant/components/hassio/services.yaml @@ -82,6 +82,12 @@ backup_full: example: "password" selector: text: + compressed: + name: Compressed + description: Use compressed archives + default: true + selector: + boolean: backup_partial: name: Create a partial backup. @@ -116,6 +122,12 @@ backup_partial: example: "password" selector: text: + compressed: + name: Compressed + description: Use compressed archives + default: true + selector: + boolean: restore_full: name: Restore from full backup. diff --git a/homeassistant/components/hassio/system_health.py b/homeassistant/components/hassio/system_health.py index 7fbc0479939..5ce38b0e121 100644 --- a/homeassistant/components/hassio/system_health.py +++ b/homeassistant/components/hassio/system_health.py @@ -15,7 +15,7 @@ def async_register( hass: HomeAssistant, register: system_health.SystemHealthRegistration ) -> None: """Register system health callbacks.""" - register.async_register_info(system_health_info, "/hassio") + register.async_register_info(system_health_info) async def system_health_info(hass: HomeAssistant): @@ -30,7 +30,6 @@ async def system_health_info(hass: HomeAssistant): healthy = { "type": "failed", "error": "Unhealthy", - "more_info": "/hassio/system", } if supervisor_info.get("supported"): @@ -39,7 +38,6 @@ async def system_health_info(hass: HomeAssistant): supported = { "type": "failed", "error": "Unsupported", - "more_info": "/hassio/system", } information = { @@ -63,7 +61,6 @@ async def system_health_info(hass: HomeAssistant): information["version_api"] = system_health.async_check_can_reach_url( hass, f"https://version.home-assistant.io/{info.get('channel')}.json", - "/hassio/system", ) information["installed_addons"] = ", ".join( diff --git a/homeassistant/components/hassio/translations/ja.json b/homeassistant/components/hassio/translations/ja.json index da3409d91cd..b7489852bdb 100644 --- a/homeassistant/components/hassio/translations/ja.json +++ b/homeassistant/components/hassio/translations/ja.json @@ -10,7 +10,7 @@ "installed_addons": "\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u306e\u30a2\u30c9\u30aa\u30f3", "supervisor_api": "Supervisor API", "supervisor_version": "Supervisor\u306e\u30d0\u30fc\u30b8\u30e7\u30f3", - "supported": "\u30b5\u30dd\u30fc\u30c8", + "supported": "\u30b5\u30dd\u30fc\u30c8\u306e\u6709\u7121", "update_channel": "\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30c1\u30e3\u30f3\u30cd\u30eb", "version_api": "\u30d0\u30fc\u30b8\u30e7\u30f3API" } diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index 9ee705c1c5e..cc4b1972bea 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -24,19 +24,11 @@ from pycec.const import ( TYPE_TUNER, ) -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - DOMAIN as MP_DOMAIN, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN from homeassistant.const import ( STATE_IDLE, STATE_OFF, @@ -185,27 +177,27 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity): """Flag media player features that are supported.""" if self.type_id == TYPE_RECORDER or self.type == TYPE_PLAYBACK: return ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY_MEDIA - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK ) if self.type == TYPE_TUNER: return ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY_MEDIA - | SUPPORT_PAUSE - | SUPPORT_STOP + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP ) if self.type_id == TYPE_AUDIO: return ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE ) - return SUPPORT_TURN_ON | SUPPORT_TURN_OFF + return MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index b9450398823..aa3f5223ebe 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -6,13 +6,8 @@ import logging from heatmiserV3 import connection, heatmiser import voluptuous as vol -from homeassistant.components.climate import ( - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - PLATFORM_SCHEMA, - ClimateEntity, -) -from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.const import ( ATTR_TEMPERATURE, CONF_HOST, @@ -76,6 +71,9 @@ def setup_platform( class HeatmiserV3Thermostat(ClimateEntity): """Representation of a HeatmiserV3 thermostat.""" + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__(self, therm, device, uh1): """Initialize the thermostat.""" self.therm = therm(device[CONF_ID], "prt", uh1) @@ -85,14 +83,9 @@ class HeatmiserV3Thermostat(ClimateEntity): self._target_temperature = None self._id = device self.dcb = None - self._hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_mode = HVACMode.HEAT self._temperature_unit = None - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE - @property def name(self): """Return the name of the thermostat, if any.""" @@ -103,22 +96,6 @@ class HeatmiserV3Thermostat(ClimateEntity): """Return the unit of measurement which this thermostat uses.""" return self._temperature_unit - @property - def hvac_mode(self) -> str: - """Return hvac operation ie. heat, cool mode. - - Need to be one of HVAC_MODE_*. - """ - return self._hvac_mode - - @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ - return [HVAC_MODE_HEAT, HVAC_MODE_OFF] - @property def current_temperature(self): """Return the current temperature.""" @@ -149,8 +126,8 @@ class HeatmiserV3Thermostat(ClimateEntity): ) self._current_temperature = int(self.therm.get_floor_temp()) self._target_temperature = int(self.therm.get_target_temp()) - self._hvac_mode = ( - HVAC_MODE_OFF + self._attr_hvac_mode = ( + HVACMode.OFF if (int(self.therm.get_current_state()) == 0) - else HVAC_MODE_HEAT + else HVACMode.HEAT ) diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 227f7737ef4..2a68ecdc7df 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -8,7 +8,10 @@ from operator import ior from pyheos import HeosError, const as heos_const from homeassistant.components import media_source -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) @@ -18,20 +21,6 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_URL, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_GROUPING, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING @@ -54,15 +43,15 @@ from .const import ( ) BASE_SUPPORTED_FEATURES = ( - SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_SHUFFLE_SET - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY_MEDIA - | SUPPORT_GROUPING - | SUPPORT_BROWSE_MEDIA + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.GROUPING + | MediaPlayerEntityFeature.BROWSE_MEDIA ) PLAY_STATE_TO_STATE = { @@ -72,11 +61,11 @@ PLAY_STATE_TO_STATE = { } CONTROL_TO_SUPPORT = { - heos_const.CONTROL_PLAY: SUPPORT_PLAY, - heos_const.CONTROL_PAUSE: SUPPORT_PAUSE, - heos_const.CONTROL_STOP: SUPPORT_STOP, - heos_const.CONTROL_PLAY_PREVIOUS: SUPPORT_PREVIOUS_TRACK, - heos_const.CONTROL_PLAY_NEXT: SUPPORT_NEXT_TRACK, + heos_const.CONTROL_PLAY: MediaPlayerEntityFeature.PLAY, + heos_const.CONTROL_PAUSE: MediaPlayerEntityFeature.PAUSE, + heos_const.CONTROL_STOP: MediaPlayerEntityFeature.STOP, + heos_const.CONTROL_PLAY_PREVIOUS: MediaPlayerEntityFeature.PREVIOUS_TRACK, + heos_const.CONTROL_PLAY_NEXT: MediaPlayerEntityFeature.NEXT_TRACK, } _LOGGER = logging.getLogger(__name__) @@ -115,7 +104,7 @@ class HeosMediaPlayer(MediaPlayerEntity): self._media_position_updated_at = None self._player = player self._signals = [] - self._supported_features = BASE_SUPPORTED_FEATURES + self._attr_supported_features = BASE_SUPPORTED_FEATURES self._source_manager = None self._group_manager = None @@ -272,7 +261,9 @@ class HeosMediaPlayer(MediaPlayerEntity): """Update supported features of the player.""" controls = self._player.now_playing_media.supported_controls current_support = [CONTROL_TO_SUPPORT[control] for control in controls] - self._supported_features = reduce(ior, current_support, BASE_SUPPORTED_FEATURES) + self._attr_supported_features = reduce( + ior, current_support, BASE_SUPPORTED_FEATURES + ) if self._group_manager is None: self._group_manager = self.hass.data[HEOS_DOMAIN][DATA_GROUP_MANAGER] @@ -419,11 +410,6 @@ class HeosMediaPlayer(MediaPlayerEntity): """State of the player.""" return PLAY_STATE_TO_STATE[self._player.state] - @property - def supported_features(self) -> int: - """Flag media player features that are supported.""" - return self._supported_features - @property def unique_id(self) -> str: """Return a unique ID.""" diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py index dde0b28632e..0ad344e3fdf 100644 --- a/homeassistant/components/here_travel_time/__init__.py +++ b/homeassistant/components/here_travel_time/__init__.py @@ -6,9 +6,11 @@ import logging import async_timeout from herepy import NoRouteFoundError, RouteMode, RoutingApi, RoutingResponse +import voluptuous as vol from homeassistant.const import ATTR_ATTRIBUTION, CONF_UNIT_SYSTEM_IMPERIAL, Platform from homeassistant.core import HomeAssistant +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.location import find_coordinates from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt @@ -64,32 +66,13 @@ class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): def _update(self) -> HERERoutingData | None: """Get the latest data from the HERE Routing API.""" - if self.config.origin_entity_id is not None: - origin = find_coordinates(self.hass, self.config.origin_entity_id) - else: - origin = self.config.origin - - if self.config.destination_entity_id is not None: - destination = find_coordinates(self.hass, self.config.destination_entity_id) - else: - destination = self.config.destination - if destination is not None and origin is not None: - here_formatted_destination = destination.split(",") - here_formatted_origin = origin.split(",") - arrival: str | None = None - departure: str | None = None - if self.config.arrival is not None: - arrival = convert_time_to_isodate(self.config.arrival) - if self.config.departure is not None: - departure = convert_time_to_isodate(self.config.departure) - - if arrival is None and departure is None: - departure = "now" + try: + origin, destination, arrival, departure = self._prepare_parameters() _LOGGER.debug( "Requesting route for origin: %s, destination: %s, route_mode: %s, mode: %s, traffic_mode: %s, arrival: %s, departure: %s", - here_formatted_origin, - here_formatted_destination, + origin, + destination, RouteMode[self.config.route_mode], RouteMode[self.config.travel_mode], RouteMode[TRAFFIC_MODE_ENABLED], @@ -98,8 +81,8 @@ class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): ) response: RoutingResponse = self._api.public_transport_timetable( - here_formatted_origin, - here_formatted_destination, + origin, + destination, True, [ RouteMode[self.config.route_mode], @@ -137,14 +120,60 @@ class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator): ATTR_DURATION_IN_TRAFFIC: traffic_time / 60, ATTR_DISTANCE: distance, ATTR_ROUTE: response.route_short, - ATTR_ORIGIN: ",".join(here_formatted_origin), - ATTR_DESTINATION: ",".join(here_formatted_destination), + ATTR_ORIGIN: ",".join(origin), + ATTR_DESTINATION: ",".join(destination), ATTR_ORIGIN_NAME: waypoint[0]["mappedRoadName"], ATTR_DESTINATION_NAME: waypoint[1]["mappedRoadName"], } ) + except InvalidCoordinatesException as ex: + _LOGGER.error("Could not call HERE api: %s", ex) return None + def _prepare_parameters( + self, + ) -> tuple[list[str], list[str], str | None, str | None]: + """Prepare parameters for the HERE api.""" + + if self.config.origin_entity_id is not None: + origin = find_coordinates(self.hass, self.config.origin_entity_id) + else: + origin = self.config.origin + + if self.config.destination_entity_id is not None: + destination = find_coordinates(self.hass, self.config.destination_entity_id) + else: + destination = self.config.destination + if destination is None: + raise InvalidCoordinatesException("Destination must be configured") + try: + here_formatted_destination = destination.split(",") + vol.Schema(cv.gps(here_formatted_destination)) + except (vol.Invalid) as ex: + raise InvalidCoordinatesException( + f"{destination} are not valid coordinates" + ) from ex + if origin is None: + raise InvalidCoordinatesException("Origin must be configured") + try: + here_formatted_origin = origin.split(",") + vol.Schema(cv.gps(here_formatted_origin)) + except (AttributeError, vol.Invalid) as ex: + raise InvalidCoordinatesException( + f"{origin} are not valid coordinates" + ) from ex + arrival: str | None = None + departure: str | None = None + if self.config.arrival is not None: + arrival = convert_time_to_isodate(self.config.arrival) + if self.config.departure is not None: + departure = convert_time_to_isodate(self.config.departure) + + if arrival is None and departure is None: + departure = "now" + + return (here_formatted_origin, here_formatted_destination, arrival, departure) + def build_hass_attribution(source_attribution: dict) -> str | None: """Build a hass frontend ready string out of the sourceAttribution.""" @@ -164,3 +193,7 @@ def convert_time_to_isodate(simple_time: time) -> str: if combined < datetime.now(): combined = combined + timedelta(days=1) return combined.isoformat() + + +class InvalidCoordinatesException(Exception): + """Coordinates for origin or destination are malformed.""" diff --git a/homeassistant/components/here_travel_time/model.py b/homeassistant/components/here_travel_time/model.py index 5cea81d1ece..eb85e966edf 100644 --- a/homeassistant/components/here_travel_time/model.py +++ b/homeassistant/components/here_travel_time/model.py @@ -31,5 +31,5 @@ class HERETravelTimeConfig: travel_mode: str route_mode: str units: str - arrival: time - departure: time + arrival: time | None + departure: time | None diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index a0449f7b5c0..304c49b6bed 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -285,6 +285,7 @@ class HERETravelTimeSensor(SensorEntity, CoordinatorEntity): """Return the attribution.""" if self.coordinator.data is not None: return self.coordinator.data.get(ATTR_ATTRIBUTION) + return None @property def icon(self) -> str: diff --git a/homeassistant/components/hisense_aehw4a1/climate.py b/homeassistant/components/hisense_aehw4a1/climate.py index 899ef06fd4c..246a04df9a8 100644 --- a/homeassistant/components/hisense_aehw4a1/climate.py +++ b/homeassistant/components/hisense_aehw4a1/climate.py @@ -1,4 +1,6 @@ """Pyaehw4a1 platform to control of Hisense AEH-W4A1 Climate Devices.""" +from __future__ import annotations + import logging from pyaehw4a1.aehw4a1 import AehW4a1 @@ -10,23 +12,16 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_BOOST, PRESET_ECO, PRESET_NONE, PRESET_SLEEP, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -40,13 +35,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import CONF_IP_ADDRESS, DOMAIN -SUPPORT_FLAGS = ( - SUPPORT_TARGET_TEMPERATURE - | SUPPORT_FAN_MODE - | SUPPORT_SWING_MODE - | SUPPORT_PRESET_MODE -) - MIN_TEMP_C = 16 MAX_TEMP_C = 32 @@ -54,11 +42,11 @@ MIN_TEMP_F = 61 MAX_TEMP_F = 90 HVAC_MODES = [ - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, + HVACMode.OFF, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.DRY, + HVACMode.FAN_ONLY, ] FAN_MODES = [ @@ -87,18 +75,18 @@ PRESET_MODES = [ ] AC_TO_HA_STATE = { - "0001": HVAC_MODE_HEAT, - "0010": HVAC_MODE_COOL, - "0011": HVAC_MODE_DRY, - "0000": HVAC_MODE_FAN_ONLY, + "0001": HVACMode.HEAT, + "0010": HVACMode.COOL, + "0011": HVACMode.DRY, + "0000": HVACMode.FAN_ONLY, } HA_STATE_TO_AC = { - HVAC_MODE_OFF: "off", - HVAC_MODE_HEAT: "mode_heat", - HVAC_MODE_COOL: "mode_cool", - HVAC_MODE_DRY: "mode_dry", - HVAC_MODE_FAN_ONLY: "mode_fan", + HVACMode.OFF: "off", + HVACMode.HEAT: "mode_heat", + HVACMode.COOL: "mode_cool", + HVACMode.DRY: "mode_dry", + HVACMode.FAN_ONLY: "mode_fan", } AC_TO_HA_FAN_MODES = { @@ -153,11 +141,18 @@ async def async_setup_entry( class ClimateAehW4a1(ClimateEntity): """Representation of a Hisense AEH-W4A1 module for climate device.""" + _attr_hvac_modes = HVAC_MODES + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.SWING_MODE + | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, device): """Initialize the climate device.""" self._unique_id = device self._device = AehW4a1(device) - self._hvac_modes = HVAC_MODES self._fan_modes = FAN_MODES self._swing_modes = SWING_MODES self._preset_modes = PRESET_MODES @@ -166,7 +161,7 @@ class ClimateAehW4a1(ClimateEntity): self._temperature_unit = None self._current_temperature = None self._target_temperature = None - self._hvac_mode = None + self._attr_hvac_mode = None self._fan_mode = None self._swing_mode = None self._preset_mode = None @@ -196,7 +191,7 @@ class ClimateAehW4a1(ClimateEntity): if self._on == "1": device_mode = status["mode_status"] - self._hvac_mode = AC_TO_HA_STATE[device_mode] + self._attr_hvac_mode = AC_TO_HA_STATE[device_mode] fan_mode = status["wind_status"] self._fan_mode = AC_TO_HA_FAN_MODES[fan_mode] @@ -204,7 +199,7 @@ class ClimateAehW4a1(ClimateEntity): swing_mode = f'{status["up_down"]}{status["left_right"]}' self._swing_mode = AC_TO_HA_SWING[swing_mode] - if self._hvac_mode in (HVAC_MODE_COOL, HVAC_MODE_HEAT): + if self._attr_hvac_mode in (HVACMode.COOL, HVACMode.HEAT): self._target_temperature = int(status["indoor_temperature_setting"], 2) else: self._target_temperature = None @@ -224,7 +219,7 @@ class ClimateAehW4a1(ClimateEntity): else: self._preset_mode = PRESET_NONE else: - self._hvac_mode = HVAC_MODE_OFF + self._attr_hvac_mode = HVACMode.OFF self._fan_mode = None self._swing_mode = None self._target_temperature = None @@ -255,16 +250,6 @@ class ClimateAehW4a1(ClimateEntity): """Return the temperature we are trying to reach.""" return self._target_temperature - @property - def hvac_mode(self): - """Return hvac target hvac state.""" - return self._hvac_mode - - @property - def hvac_modes(self): - """Return the list of available operation modes.""" - return self._hvac_modes - @property def fan_mode(self): """Return the fan setting.""" @@ -319,11 +304,6 @@ class ClimateAehW4a1(ClimateEntity): """Return the supported step of target temperature.""" return 1 - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - async def async_set_temperature(self, **kwargs): """Set new target temperatures.""" if self._on != "1": @@ -345,8 +325,8 @@ class ClimateAehW4a1(ClimateEntity): if self._on != "1": _LOGGER.warning("AC at %s is off, could not set fan mode", self._unique_id) return - if self._hvac_mode in (HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY) and ( - self._hvac_mode != HVAC_MODE_FAN_ONLY or fan_mode != FAN_AUTO + if self._attr_hvac_mode in (HVACMode.COOL, HVACMode.FAN_ONLY) and ( + self._attr_hvac_mode != HVACMode.FAN_ONLY or fan_mode != FAN_AUTO ): _LOGGER.debug("Setting fan mode of %s to %s", self._unique_id, fan_mode) await self._device.command(HA_FAN_MODES_TO_AC[fan_mode]) @@ -403,16 +383,16 @@ class ClimateAehW4a1(ClimateEntity): self._previous_state = preset_mode elif preset_mode == PRESET_SLEEP: await self._device.command("sleep_1") - self._previous_state = self._hvac_mode + self._previous_state = self._attr_hvac_mode elif preset_mode == "sleep_2": await self._device.command("sleep_2") - self._previous_state = self._hvac_mode + self._previous_state = self._attr_hvac_mode elif preset_mode == "sleep_3": await self._device.command("sleep_3") - self._previous_state = self._hvac_mode + self._previous_state = self._attr_hvac_mode elif preset_mode == "sleep_4": await self._device.command("sleep_4") - self._previous_state = self._hvac_mode + self._previous_state = self._attr_hvac_mode elif self._previous_state is not None: if self._previous_state == PRESET_ECO: await self._device.command("energysave_off") @@ -422,10 +402,10 @@ class ClimateAehW4a1(ClimateEntity): await self._device.command(HA_STATE_TO_AC[self._previous_state]) self._previous_state = None - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new operation mode.""" _LOGGER.debug("Setting operation mode of %s to %s", self._unique_id, hvac_mode) - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_turn_off() else: await self._device.command(HA_STATE_TO_AC[hvac_mode]) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 2bf285d25e6..8740f352dee 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -79,20 +79,6 @@ def get_last_state_changes(hass, number_of_states, entity_id): return history.get_last_state_changes(hass, number_of_states, entity_id) -@deprecated_function("homeassistant.components.recorder.history.get_states") -def get_states(hass, utc_point_in_time, entity_ids=None, run=None, filters=None): - """Return the states at a specific point in time.""" - return history.get_states( - hass, utc_point_in_time, entity_ids=None, run=None, filters=None - ) - - -@deprecated_function("homeassistant.components.recorder.history.get_state") -def get_state(hass, utc_point_in_time, entity_id, run=None): - """Return a state at a specific point in time.""" - return history.get_state(hass, utc_point_in_time, entity_id, run=None) - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the history hooks.""" conf = config.get(DOMAIN, {}) diff --git a/homeassistant/components/history_stats/coordinator.py b/homeassistant/components/history_stats/coordinator.py new file mode 100644 index 00000000000..2a2cc392bb5 --- /dev/null +++ b/homeassistant/components/history_stats/coordinator.py @@ -0,0 +1,96 @@ +"""History stats data coordinator.""" +from __future__ import annotations + +from datetime import timedelta +import logging +from typing import Any + +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback +from homeassistant.exceptions import TemplateError +from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.helpers.start import async_at_start +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .data import HistoryStats, HistoryStatsState + +_LOGGER = logging.getLogger(__name__) + + +UPDATE_INTERVAL = timedelta(minutes=1) + + +class HistoryStatsUpdateCoordinator(DataUpdateCoordinator): + """DataUpdateCoordinator to gather data for a specific TPLink device.""" + + data: HistoryStatsState + + def __init__( + self, + hass: HomeAssistant, + history_stats: HistoryStats, + name: str, + ) -> None: + """Initialize DataUpdateCoordinator.""" + self._history_stats = history_stats + self._subscriber_count = 0 + self._at_start_listener: CALLBACK_TYPE | None = None + self._track_events_listener: CALLBACK_TYPE | None = None + super().__init__( + hass, + _LOGGER, + name=name, + update_interval=UPDATE_INTERVAL, + ) + + @callback + def async_setup_state_listener(self) -> CALLBACK_TYPE: + """Set up listeners and return a callback to cancel them.""" + + @callback + def remove_listener() -> None: + """Remove update listener.""" + self._subscriber_count -= 1 + if self._subscriber_count == 0: + self._async_remove_listener() + + if self._subscriber_count == 0: + self._async_add_listener() + self._subscriber_count += 1 + + return remove_listener + + @callback + def _async_remove_listener(self) -> None: + """Remove state change listener.""" + if self._track_events_listener: + self._track_events_listener() + self._track_events_listener = None + if self._at_start_listener: + self._at_start_listener() + self._at_start_listener = None + + @callback + def _async_add_listener(self) -> None: + """Add a listener to start tracking state changes after start.""" + self._at_start_listener = async_at_start( + self.hass, self._async_add_events_listener + ) + + @callback + def _async_add_events_listener(self, *_: Any) -> None: + """Handle hass starting and start tracking events.""" + self._at_start_listener = None + self._track_events_listener = async_track_state_change_event( + self.hass, [self._history_stats.entity_id], self._async_update_from_event + ) + + async def _async_update_from_event(self, event: Event) -> None: + """Process an update from an event.""" + self.async_set_updated_data(await self._history_stats.async_update(event)) + + async def _async_update_data(self) -> HistoryStatsState: + """Fetch update the history stats state.""" + try: + return await self._history_stats.async_update(None) + except (TemplateError, TypeError, ValueError) as ex: + raise UpdateFailed(ex) from ex diff --git a/homeassistant/components/history_stats/data.py b/homeassistant/components/history_stats/data.py new file mode 100644 index 00000000000..3f22f4cc32b --- /dev/null +++ b/homeassistant/components/history_stats/data.py @@ -0,0 +1,177 @@ +"""Manage the history_stats data.""" +from __future__ import annotations + +from dataclasses import dataclass +import datetime + +from homeassistant.components.recorder import get_instance, history +from homeassistant.core import Event, HomeAssistant, State +from homeassistant.helpers.template import Template +import homeassistant.util.dt as dt_util + +from .helpers import async_calculate_period, floored_timestamp + +MIN_TIME_UTC = datetime.datetime.min.replace(tzinfo=dt_util.UTC) + + +@dataclass +class HistoryStatsState: + """The current stats of the history stats.""" + + hours_matched: float | None + changes_to_match_state: int | None + period: tuple[datetime.datetime, datetime.datetime] + + +class HistoryStats: + """Manage history stats.""" + + def __init__( + self, + hass: HomeAssistant, + entity_id: str, + entity_states: list[str], + start: Template | None, + end: Template | None, + duration: datetime.timedelta | None, + ) -> None: + """Init the history stats manager.""" + self.hass = hass + self.entity_id = entity_id + self._period = (MIN_TIME_UTC, MIN_TIME_UTC) + self._state: HistoryStatsState = HistoryStatsState(None, None, self._period) + self._history_current_period: list[State] = [] + self._previous_run_before_start = False + self._entity_states = set(entity_states) + self._duration = duration + self._start = start + self._end = end + + async def async_update(self, event: Event | None) -> HistoryStatsState: + """Update the stats at a given time.""" + # Get previous values of start and end + previous_period_start, previous_period_end = self._period + # Parse templates + self._period = async_calculate_period(self._duration, self._start, self._end) + # Get the current period + current_period_start, current_period_end = self._period + + # Convert times to UTC + current_period_start = dt_util.as_utc(current_period_start) + current_period_end = dt_util.as_utc(current_period_end) + previous_period_start = dt_util.as_utc(previous_period_start) + previous_period_end = dt_util.as_utc(previous_period_end) + + # Compute integer timestamps + current_period_start_timestamp = floored_timestamp(current_period_start) + current_period_end_timestamp = floored_timestamp(current_period_end) + previous_period_start_timestamp = floored_timestamp(previous_period_start) + previous_period_end_timestamp = floored_timestamp(previous_period_end) + now_timestamp = floored_timestamp(datetime.datetime.now()) + + if now_timestamp < current_period_start_timestamp: + # History cannot tell the future + self._history_current_period = [] + self._previous_run_before_start = True + + # + # We avoid querying the database if the below did NOT happen: + # + # - The previous run happened before the start time + # - The start time changed + # - The period shrank in size + # - The previous period ended before now + # + elif ( + not self._previous_run_before_start + and current_period_start_timestamp == previous_period_start_timestamp + and ( + current_period_end_timestamp == previous_period_end_timestamp + or ( + current_period_end_timestamp >= previous_period_end_timestamp + and previous_period_end_timestamp <= now_timestamp + ) + ) + ): + new_data = False + if event and event.data["new_state"] is not None: + new_state: State = event.data["new_state"] + if current_period_start <= new_state.last_changed <= current_period_end: + self._history_current_period.append(new_state) + new_data = True + if not new_data and current_period_end_timestamp < now_timestamp: + # If period has not changed and current time after the period end... + # Don't compute anything as the value cannot have changed + return self._state + else: + self._history_current_period = await get_instance( + self.hass + ).async_add_executor_job( + self._update_from_database, + current_period_start, + current_period_end, + ) + self._previous_run_before_start = False + + if not self._history_current_period: + self._state = HistoryStatsState(None, None, self._period) + return self._state + + hours_matched, changes_to_match_state = self._async_compute_hours_and_changes( + now_timestamp, + current_period_start_timestamp, + current_period_end_timestamp, + ) + self._state = HistoryStatsState( + hours_matched, changes_to_match_state, self._period + ) + return self._state + + def _update_from_database( + self, start: datetime.datetime, end: datetime.datetime + ) -> list[State]: + return history.state_changes_during_period( + self.hass, + start, + end, + self.entity_id, + include_start_time_state=True, + no_attributes=True, + ).get(self.entity_id, []) + + def _async_compute_hours_and_changes( + self, now_timestamp: float, start_timestamp: float, end_timestamp: float + ) -> tuple[float, int]: + """Compute the hours matched and changes from the history list and first state.""" + # state_changes_during_period is called with include_start_time_state=True + # which is the default and always provides the state at the start + # of the period + previous_state_matches = ( + self._history_current_period + and self._history_current_period[0].state in self._entity_states + ) + last_state_change_timestamp = start_timestamp + elapsed = 0.0 + changes_to_match_state = 0 + + # Make calculations + for item in self._history_current_period: + current_state_matches = item.state in self._entity_states + state_change_timestamp = item.last_changed.timestamp() + + if previous_state_matches: + elapsed += state_change_timestamp - last_state_change_timestamp + elif current_state_matches: + changes_to_match_state += 1 + + previous_state_matches = current_state_matches + last_state_change_timestamp = state_change_timestamp + + # Count time elapsed between last history state and end of measure + if previous_state_matches: + measure_end = min(end_timestamp, now_timestamp) + elapsed += measure_end - last_state_change_timestamp + + # Save value in hours + hours_matched = elapsed / 3600 + return hours_matched, changes_to_match_state diff --git a/homeassistant/components/history_stats/helpers.py b/homeassistant/components/history_stats/helpers.py new file mode 100644 index 00000000000..23143984f48 --- /dev/null +++ b/homeassistant/components/history_stats/helpers.py @@ -0,0 +1,88 @@ +"""Helpers to make instant statistics about your history.""" +from __future__ import annotations + +import datetime +import logging +import math + +from homeassistant.core import callback +from homeassistant.exceptions import TemplateError +from homeassistant.helpers.template import Template +import homeassistant.util.dt as dt_util + +_LOGGER = logging.getLogger(__name__) + + +DURATION_START = "start" +DURATION_END = "end" + + +@callback +def async_calculate_period( + duration: datetime.timedelta | None, + start_template: Template | None, + end_template: Template | None, +) -> tuple[datetime.datetime, datetime.datetime]: + """Parse the templates and return the period.""" + bounds: dict[str, datetime.datetime | None] = { + DURATION_START: None, + DURATION_END: None, + } + for bound, template in ( + (DURATION_START, start_template), + (DURATION_END, end_template), + ): + # Parse start + if template is None: + continue + try: + rendered = template.async_render() + except (TemplateError, TypeError) as ex: + if ex.args and not ex.args[0].startswith( + "UndefinedError: 'None' has no attribute" + ): + _LOGGER.error("Error parsing template for field %s", bound, exc_info=ex) + raise + if isinstance(rendered, str): + bounds[bound] = dt_util.parse_datetime(rendered) + if bounds[bound] is not None: + continue + try: + bounds[bound] = dt_util.as_local( + dt_util.utc_from_timestamp(math.floor(float(rendered))) + ) + except ValueError as ex: + raise ValueError( + f"Parsing error: {bound} must be a datetime or a timestamp: {ex}" + ) from ex + + start = bounds[DURATION_START] + end = bounds[DURATION_END] + + # Calculate start or end using the duration + if start is None: + assert end is not None + assert duration is not None + start = end - duration + if end is None: + assert start is not None + assert duration is not None + end = start + duration + + return start, end + + +def pretty_ratio( + value: float, period: tuple[datetime.datetime, datetime.datetime] +) -> float: + """Format the ratio of value / period duration.""" + if len(period) != 2 or period[0] == period[1]: + return 0.0 + + ratio = 100 * 3600 * value / (period[1] - period[0]).total_seconds() + return round(ratio, 1) + + +def floored_timestamp(incoming_dt: datetime.datetime) -> float: + """Calculate the floored value of a timestamp.""" + return math.floor(dt_util.as_timestamp(incoming_dt)) diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 5177d5f5239..b3e64106d9f 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -1,35 +1,37 @@ """Component to make instant statistics about your history.""" from __future__ import annotations +from abc import abstractmethod import datetime -import logging -import math import voluptuous as vol -from homeassistant.components.recorder import get_instance, history -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA, + SensorDeviceClass, + SensorEntity, + SensorStateClass, +) from homeassistant.const import ( CONF_ENTITY_ID, CONF_NAME, CONF_STATE, CONF_TYPE, - EVENT_HOMEASSISTANT_START, PERCENTAGE, TIME_HOURS, ) -from homeassistant.core import CoreState, HomeAssistant, callback -from homeassistant.exceptions import TemplateError +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -import homeassistant.util.dt as dt_util +from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import DOMAIN, PLATFORMS - -_LOGGER = logging.getLogger(__name__) +from .coordinator import HistoryStatsUpdateCoordinator +from .data import HistoryStats +from .helpers import pretty_ratio CONF_START = "start" CONF_END = "end" @@ -42,15 +44,13 @@ CONF_TYPE_COUNT = "count" CONF_TYPE_KEYS = [CONF_TYPE_TIME, CONF_TYPE_RATIO, CONF_TYPE_COUNT] DEFAULT_NAME = "unnamed statistics" -UNITS = { +UNITS: dict[str, str] = { CONF_TYPE_TIME: TIME_HOURS, CONF_TYPE_RATIO: PERCENTAGE, CONF_TYPE_COUNT: "", } ICON = "mdi:chart-line" -ATTR_VALUE = "value" - def exactly_two_period_keys(conf): """Ensure exactly 2 of CONF_PERIOD_KEYS are provided.""" @@ -87,281 +87,83 @@ async def async_setup_platform( """Set up the History Stats sensor.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - entity_id = config.get(CONF_ENTITY_ID) - entity_states = config.get(CONF_STATE) - start = config.get(CONF_START) - end = config.get(CONF_END) - duration = config.get(CONF_DURATION) - sensor_type = config.get(CONF_TYPE) - name = config.get(CONF_NAME) + entity_id: str = config[CONF_ENTITY_ID] + entity_states: list[str] = config[CONF_STATE] + start: Template | None = config.get(CONF_START) + end: Template | None = config.get(CONF_END) + duration: datetime.timedelta | None = config.get(CONF_DURATION) + sensor_type: str = config[CONF_TYPE] + name: str = config[CONF_NAME] for template in (start, end): if template is not None: template.hass = hass - async_add_entities( - [ - HistoryStatsSensor( - hass, entity_id, entity_states, start, end, duration, sensor_type, name - ) - ] - ) + history_stats = HistoryStats(hass, entity_id, entity_states, start, end, duration) + coordinator = HistoryStatsUpdateCoordinator(hass, history_stats, name) + async_add_entities([HistoryStatsSensor(coordinator, sensor_type, name)]) -class HistoryStatsSensor(SensorEntity): - """Representation of a HistoryStats sensor.""" +class HistoryStatsSensorBase( + CoordinatorEntity[HistoryStatsUpdateCoordinator], SensorEntity +): + """Base class for a HistoryStats sensor.""" + + _attr_icon = ICON def __init__( - self, hass, entity_id, entity_states, start, end, duration, sensor_type, name - ): + self, + coordinator: HistoryStatsUpdateCoordinator, + name: str, + ) -> None: + """Initialize the HistoryStats sensor base class.""" + super().__init__(coordinator) + self._attr_name = name + + async def async_added_to_hass(self) -> None: + """Entity has been added to hass.""" + await super().async_added_to_hass() + self.async_on_remove(self.coordinator.async_setup_state_listener()) + + def _handle_coordinator_update(self) -> None: + """Set attrs from value and count.""" + self._process_update() + super()._handle_coordinator_update() + + @callback + @abstractmethod + def _process_update(self) -> None: + """Process an update from the coordinator.""" + + +class HistoryStatsSensor(HistoryStatsSensorBase): + """A HistoryStats sensor.""" + + _attr_device_class = SensorDeviceClass.DURATION + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__( + self, + coordinator: HistoryStatsUpdateCoordinator, + sensor_type: str, + name: str, + ) -> None: """Initialize the HistoryStats sensor.""" - self._entity_id = entity_id - self._entity_states = entity_states - self._duration = duration - self._start = start - self._end = end + super().__init__(coordinator, name) + self._attr_native_unit_of_measurement = UNITS[sensor_type] self._type = sensor_type - self._name = name - self._unit_of_measurement = UNITS[sensor_type] - self._period = (datetime.datetime.now(), datetime.datetime.now()) - self.value = None - self.count = None - - async def async_added_to_hass(self): - """Create listeners when the entity is added.""" - - @callback - def start_refresh(*args): - """Register state tracking.""" - - @callback - def force_refresh(*args): - """Force the component to refresh.""" - self.async_schedule_update_ha_state(True) - - force_refresh() - self.async_on_remove( - async_track_state_change_event( - self.hass, [self._entity_id], force_refresh - ) - ) - - if self.hass.state == CoreState.running: - start_refresh() + @callback + def _process_update(self) -> None: + """Process an update from the coordinator.""" + state = self.coordinator.data + if state is None or state.hours_matched is None: + self._attr_native_value = None return - # Delay first refresh to keep startup fast - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_refresh) - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def native_value(self): - """Return the state of the sensor.""" - if self.value is None or self.count is None: - return None - if self._type == CONF_TYPE_TIME: - return round(self.value, 2) - - if self._type == CONF_TYPE_RATIO: - return HistoryStatsHelper.pretty_ratio(self.value, self._period) - - if self._type == CONF_TYPE_COUNT: - return self.count - - @property - def native_unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit_of_measurement - - @property - def extra_state_attributes(self): - """Return the state attributes of the sensor.""" - if self.value is None: - return {} - - hsh = HistoryStatsHelper - return {ATTR_VALUE: hsh.pretty_duration(self.value)} - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return ICON - - async def async_update(self): - """Get the latest data and updates the states.""" - # Get previous values of start and end - p_start, p_end = self._period - - # Parse templates - self.update_period() - start, end = self._period - - # Convert times to UTC - start = dt_util.as_utc(start) - end = dt_util.as_utc(end) - p_start = dt_util.as_utc(p_start) - p_end = dt_util.as_utc(p_end) - now = datetime.datetime.now() - - # Compute integer timestamps - start_timestamp = math.floor(dt_util.as_timestamp(start)) - end_timestamp = math.floor(dt_util.as_timestamp(end)) - p_start_timestamp = math.floor(dt_util.as_timestamp(p_start)) - p_end_timestamp = math.floor(dt_util.as_timestamp(p_end)) - now_timestamp = math.floor(dt_util.as_timestamp(now)) - - # If period has not changed and current time after the period end... - if ( - start_timestamp == p_start_timestamp - and end_timestamp == p_end_timestamp - and end_timestamp <= now_timestamp - ): - # Don't compute anything as the value cannot have changed - return - - await get_instance(self.hass).async_add_executor_job( - self._update, start, end, now_timestamp, start_timestamp, end_timestamp - ) - - def _update(self, start, end, now_timestamp, start_timestamp, end_timestamp): - # Get history between start and end - history_list = history.state_changes_during_period( - self.hass, start, end, str(self._entity_id), no_attributes=True - ) - - if self._entity_id not in history_list: - return - - # Get the first state - last_state = history.get_state( - self.hass, start, self._entity_id, no_attributes=True - ) - last_state = last_state is not None and last_state in self._entity_states - last_time = start_timestamp - elapsed = 0 - count = 0 - - # Make calculations - for item in history_list.get(self._entity_id): - current_state = item.state in self._entity_states - current_time = item.last_changed.timestamp() - - if last_state: - elapsed += current_time - last_time - if current_state and not last_state: - count += 1 - - last_state = current_state - last_time = current_time - - # Count time elapsed between last history state and end of measure - if last_state: - measure_end = min(end_timestamp, now_timestamp) - elapsed += measure_end - last_time - - # Save value in hours - self.value = elapsed / 3600 - - # Save counter - self.count = count - - def update_period(self): - """Parse the templates and store a datetime tuple in _period.""" - start = None - end = None - - # Parse start - if self._start is not None: - try: - start_rendered = self._start.async_render() - except (TemplateError, TypeError) as ex: - HistoryStatsHelper.handle_template_exception(ex, "start") - return - if isinstance(start_rendered, str): - start = dt_util.parse_datetime(start_rendered) - if start is None: - try: - start = dt_util.as_local( - dt_util.utc_from_timestamp(math.floor(float(start_rendered))) - ) - except ValueError: - _LOGGER.error( - "Parsing error: start must be a datetime or a timestamp" - ) - return - - # Parse end - if self._end is not None: - try: - end_rendered = self._end.async_render() - except (TemplateError, TypeError) as ex: - HistoryStatsHelper.handle_template_exception(ex, "end") - return - if isinstance(end_rendered, str): - end = dt_util.parse_datetime(end_rendered) - if end is None: - try: - end = dt_util.as_local( - dt_util.utc_from_timestamp(math.floor(float(end_rendered))) - ) - except ValueError: - _LOGGER.error( - "Parsing error: end must be a datetime or a timestamp" - ) - return - - # Calculate start or end using the duration - if start is None: - start = end - self._duration - if end is None: - end = start + self._duration - - if start > dt_util.now(): - # History hasn't been written yet for this period - return - if dt_util.now() < end: - # No point in making stats of the future - end = dt_util.now() - - self._period = start, end - - -class HistoryStatsHelper: - """Static methods to make the HistoryStatsSensor code lighter.""" - - @staticmethod - def pretty_duration(hours): - """Format a duration in days, hours, minutes, seconds.""" - seconds = int(3600 * hours) - days, seconds = divmod(seconds, 86400) - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - if days > 0: - return "%dd %dh %dm" % (days, hours, minutes) - if hours > 0: - return "%dh %dm" % (hours, minutes) - return "%dm" % minutes - - @staticmethod - def pretty_ratio(value, period): - """Format the ratio of value / period duration.""" - if len(period) != 2 or period[0] == period[1]: - return 0.0 - - ratio = 100 * 3600 * value / (period[1] - period[0]).total_seconds() - return round(ratio, 1) - - @staticmethod - def handle_template_exception(ex, field): - """Log an error nicely if the template cannot be interpreted.""" - if ex.args and ex.args[0].startswith("UndefinedError: 'None' has no attribute"): - # Common during HA startup - so just a warning - _LOGGER.warning(ex) - return - _LOGGER.error("Error parsing template for field %s", field, exc_info=ex) + self._attr_native_value = round(state.hours_matched, 2) + elif self._type == CONF_TYPE_RATIO: + self._attr_native_value = pretty_ratio(state.hours_matched, state.period) + elif self._type == CONF_TYPE_COUNT: + self._attr_native_value = state.changes_to_match_state diff --git a/homeassistant/components/hive/alarm_control_panel.py b/homeassistant/components/hive/alarm_control_panel.py index 4a0ad577f90..a3509fce66f 100644 --- a/homeassistant/components/hive/alarm_control_panel.py +++ b/homeassistant/components/hive/alarm_control_panel.py @@ -1,10 +1,9 @@ """Support for the Hive alarm.""" from datetime import timedelta -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_NIGHT, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -46,6 +45,10 @@ class HiveAlarmControlPanelEntity(HiveEntity, AlarmControlPanelEntity): """Representation of a Hive alarm.""" _attr_icon = ICON + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.ARM_AWAY + ) @property def unique_id(self): @@ -81,11 +84,6 @@ class HiveAlarmControlPanelEntity(HiveEntity, AlarmControlPanelEntity): return STATE_ALARM_TRIGGERED return HIVETOHA[self.device["status"]["mode"]] - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_NIGHT | SUPPORT_ALARM_ARM_AWAY - async def async_alarm_disarm(self, code=None): """Send disarm command.""" await self.hive.alarm.setMode(self.device, "home") diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index d6da82168e5..d094ca9eace 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -6,16 +6,11 @@ import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_BOOST, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -33,27 +28,25 @@ from .const import ( ) HIVE_TO_HASS_STATE = { - "SCHEDULE": HVAC_MODE_AUTO, - "MANUAL": HVAC_MODE_HEAT, - "OFF": HVAC_MODE_OFF, + "SCHEDULE": HVACMode.AUTO, + "MANUAL": HVACMode.HEAT, + "OFF": HVACMode.OFF, } HASS_TO_HIVE_STATE = { - HVAC_MODE_AUTO: "SCHEDULE", - HVAC_MODE_HEAT: "MANUAL", - HVAC_MODE_OFF: "OFF", + HVACMode.AUTO: "SCHEDULE", + HVACMode.HEAT: "MANUAL", + HVACMode.OFF: "OFF", } HIVE_TO_HASS_HVAC_ACTION = { - "UNKNOWN": CURRENT_HVAC_OFF, - False: CURRENT_HVAC_IDLE, - True: CURRENT_HVAC_HEAT, + "UNKNOWN": HVACAction.OFF, + False: HVACAction.IDLE, + True: HVACAction.HEATING, } TEMP_UNIT = {"C": TEMP_CELSIUS, "F": TEMP_FAHRENHEIT} -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE -SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] SUPPORT_PRESET = [PRESET_NONE, PRESET_BOOST] PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) @@ -111,6 +104,11 @@ async def async_setup_entry( class HiveClimateEntity(HiveEntity, ClimateEntity): """Hive Climate Device.""" + _attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF] + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, hive_session, hive_device): """Initialize the Climate device.""" super().__init__(hive_session, hive_device) @@ -134,11 +132,6 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): via_device=(DOMAIN, self.device["parentDevice"]), ) - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - @property def name(self): """Return the name of the Climate device.""" @@ -150,15 +143,7 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): return self.device["deviceData"]["online"] @property - def hvac_modes(self): - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ - return SUPPORT_HVAC - - @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. @@ -166,7 +151,7 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): return HIVE_TO_HASS_STATE[self.device["status"]["mode"]] @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return current HVAC action.""" return HIVE_TO_HASS_HVAC_ACTION[self.device["status"]["action"]] @@ -208,7 +193,7 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): return SUPPORT_PRESET @refresh_system - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" new_mode = HASS_TO_HIVE_STATE[hvac_mode] await self.hive.heating.setMode(self.device, new_mode) diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 89a59c1f4b0..ba095896b64 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -1,13 +1,13 @@ """Support for Hive light devices.""" +from __future__ import annotations + from datetime import timedelta from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -138,17 +138,28 @@ class HiveDeviceLight(HiveEntity, LightEntity): await self.hive.light.turnOff(self.device) @property - def supported_features(self): - """Flag supported features.""" - supported_features = None + def color_mode(self) -> str: + """Return the color mode of the light.""" if self.device["hiveType"] == "warmwhitelight": - supported_features = SUPPORT_BRIGHTNESS - elif self.device["hiveType"] == "tuneablelight": - supported_features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP - elif self.device["hiveType"] == "colourtuneablelight": - supported_features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR + return ColorMode.BRIGHTNESS + if self.device["hiveType"] == "tuneablelight": + return ColorMode.COLOR_TEMP + if self.device["hiveType"] == "colourtuneablelight": + if self.device["status"]["mode"] == "COLOUR": + return ColorMode.HS + return ColorMode.COLOR_TEMP + return ColorMode.ONOFF - return supported_features + @property + def supported_color_modes(self) -> set[str] | None: + """Flag supported color modes.""" + if self.device["hiveType"] == "warmwhitelight": + return {ColorMode.BRIGHTNESS} + if self.device["hiveType"] == "tuneablelight": + return {ColorMode.COLOR_TEMP} + if self.device["hiveType"] == "colourtuneablelight": + return {ColorMode.COLOR_TEMP, ColorMode.HS} + return {ColorMode.ONOFF} async def async_update(self): """Update all Node data from Hive.""" diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index deb36e0b0f2..5e3c18fce69 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -5,8 +5,8 @@ import voluptuous as vol from homeassistant.components.water_heater import ( STATE_ECO, - SUPPORT_OPERATION_MODE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON, TEMP_CELSIUS @@ -24,7 +24,6 @@ from .const import ( WATER_HEATER_MODES, ) -SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE HOTWATER_NAME = "Hot Water" PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) @@ -75,6 +74,8 @@ async def async_setup_entry( class HiveWaterHeater(HiveEntity, WaterHeaterEntity): """Hive Water Heater Device.""" + _attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE + @property def unique_id(self): """Return unique ID of entity.""" @@ -92,11 +93,6 @@ class HiveWaterHeater(HiveEntity, WaterHeaterEntity): via_device=(DOMAIN, self.device["parentDevice"]), ) - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS_HEATER - @property def name(self): """Return the name of the water heater.""" diff --git a/homeassistant/components/home_connect/light.py b/homeassistant/components/home_connect/light.py index 626ceda32fe..d1586450881 100644 --- a/homeassistant/components/home_connect/light.py +++ b/homeassistant/components/home_connect/light.py @@ -7,8 +7,7 @@ from homeconnect.api import HomeConnectError from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -68,11 +67,15 @@ class HomeConnectLight(HomeConnectEntity, LightEntity): self._key = BSH_AMBIENT_LIGHT_ENABLED self._custom_color_key = BSH_AMBIENT_LIGHT_CUSTOM_COLOR self._color_key = BSH_AMBIENT_LIGHT_COLOR + self._attr_color_mode = ColorMode.HS + self._attr_supported_color_modes = {ColorMode.HS} else: self._brightness_key = COOKING_LIGHTING_BRIGHTNESS self._key = COOKING_LIGHTING self._custom_color_key = None self._color_key = None + self._attr_color_mode = ColorMode.BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} @property def is_on(self): @@ -89,13 +92,6 @@ class HomeConnectLight(HomeConnectEntity, LightEntity): """Return the color property.""" return self._hs_color - @property - def supported_features(self): - """Flag supported features.""" - if self._ambient: - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR - return SUPPORT_BRIGHTNESS - async def async_turn_on(self, **kwargs): """Switch the light on, change brightness, change color.""" if self._ambient: diff --git a/homeassistant/components/home_connect/manifest.json b/homeassistant/components/home_connect/manifest.json index 784042f884c..e50053d2d1b 100644 --- a/homeassistant/components/home_connect/manifest.json +++ b/homeassistant/components/home_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "home_connect", "name": "Home Connect", "documentation": "https://www.home-assistant.io/integrations/home_connect", - "dependencies": ["http"], + "dependencies": ["auth"], "codeowners": ["@DavidMStraub"], "requirements": ["homeconnect==0.7.0"], "config_flow": true, diff --git a/homeassistant/components/home_connect/translations/hu.json b/homeassistant/components/home_connect/translations/hu.json index ca5f3e1e9ae..77b76df14d7 100644 --- a/homeassistant/components/home_connect/translations/hu.json +++ b/homeassistant/components/home_connect/translations/hu.json @@ -2,14 +2,14 @@ "config": { "abort": { "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3." }, "create_entry": { "default": "Sikeres hiteles\u00edt\u00e9s" }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/home_connect/translations/it.json b/homeassistant/components/home_connect/translations/it.json index 3dc834bfd85..74a598757d8 100644 --- a/homeassistant/components/home_connect/translations/it.json +++ b/homeassistant/components/home_connect/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})" }, "create_entry": { diff --git a/homeassistant/components/home_plus_control/manifest.json b/homeassistant/components/home_plus_control/manifest.json index 30ef34b6b34..bbfde5bbded 100644 --- a/homeassistant/components/home_plus_control/manifest.json +++ b/homeassistant/components/home_plus_control/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/home_plus_control", "requirements": ["homepluscontrol==0.0.5"], - "dependencies": ["http"], + "dependencies": ["auth"], "codeowners": ["@chemaaa"], "iot_class": "cloud_polling", "loggers": ["homepluscontrol"] diff --git a/homeassistant/components/home_plus_control/translations/hu.json b/homeassistant/components/home_plus_control/translations/hu.json index 09625a222f2..3dc4b451332 100644 --- a/homeassistant/components/home_plus_control/translations/hu.json +++ b/homeassistant/components/home_plus_control/translations/hu.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "create_entry": { @@ -13,7 +13,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } }, diff --git a/homeassistant/components/home_plus_control/translations/it.json b/homeassistant/components/home_plus_control/translations/it.json index 789a7db85eb..e79d22275f7 100644 --- a/homeassistant/components/home_plus_control/translations/it.json +++ b/homeassistant/components/home_plus_control/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "L'account \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 91629cc9933..e6a4b90dbe8 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -36,6 +36,8 @@ _LOGGER = logging.getLogger(__name__) CONF_ENTITY_ID = "entity_id" CONF_FROM = "from" CONF_TO = "to" +CONF_NOT_FROM = "not_from" +CONF_NOT_TO = "not_to" BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( { @@ -49,15 +51,19 @@ BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( TRIGGER_STATE_SCHEMA = BASE_SCHEMA.extend( { # These are str on purpose. Want to catch YAML conversions - vol.Optional(CONF_FROM): vol.Any(str, [str], None), - vol.Optional(CONF_TO): vol.Any(str, [str], None), + vol.Exclusive(CONF_FROM, CONF_FROM): vol.Any(str, [str], None), + vol.Exclusive(CONF_NOT_FROM, CONF_FROM): vol.Any(str, [str], None), + vol.Exclusive(CONF_TO, CONF_TO): vol.Any(str, [str], None), + vol.Exclusive(CONF_NOT_TO, CONF_TO): vol.Any(str, [str], None), } ) TRIGGER_ATTRIBUTE_SCHEMA = BASE_SCHEMA.extend( { - vol.Optional(CONF_FROM): cv.match_all, - vol.Optional(CONF_TO): cv.match_all, + vol.Exclusive(CONF_FROM, CONF_FROM): cv.match_all, + vol.Exclusive(CONF_NOT_FROM, CONF_FROM): cv.match_all, + vol.Exclusive(CONF_TO, CONF_TO): cv.match_all, + vol.Exclusive(CONF_NOT_TO, CONF_TO): cv.match_all, } ) @@ -94,19 +100,30 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" entity_ids = config[CONF_ENTITY_ID] - if (from_state := config.get(CONF_FROM)) is None: - from_state = MATCH_ALL - if (to_state := config.get(CONF_TO)) is None: - to_state = MATCH_ALL + + if (from_state := config.get(CONF_FROM)) is not None: + match_from_state = process_state_match(from_state) + elif (not_from_state := config.get(CONF_NOT_FROM)) is not None: + match_from_state = process_state_match(not_from_state, invert=True) + else: + match_from_state = process_state_match(MATCH_ALL) + + if (to_state := config.get(CONF_TO)) is not None: + match_to_state = process_state_match(to_state) + elif (not_to_state := config.get(CONF_NOT_TO)) is not None: + match_to_state = process_state_match(not_to_state, invert=True) + else: + match_to_state = process_state_match(MATCH_ALL) + time_delta = config.get(CONF_FOR) template.attach(hass, time_delta) # If neither CONF_FROM or CONF_TO are specified, # fire on all changes to the state or an attribute - match_all = CONF_FROM not in config and CONF_TO not in config + match_all = all( + item not in config for item in (CONF_FROM, CONF_NOT_FROM, CONF_NOT_TO, CONF_TO) + ) unsub_track_same = {} period: dict[str, timedelta] = {} - match_from_state = process_state_match(from_state) - match_to_state = process_state_match(to_state) attribute = config.get(CONF_ATTRIBUTE) job = HassJob(action) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index af21bbdaf84..48f8f336fe7 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -12,6 +12,7 @@ from uuid import UUID from aiohttp import web from pyhap.const import STANDALONE_AID +from pyhap.loader import get_loader import voluptuous as vol from zeroconf.asyncio import AsyncZeroconf @@ -204,6 +205,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the HomeKit from yaml.""" hass.data.setdefault(DOMAIN, {})[PERSIST_LOCK] = asyncio.Lock() + # Initialize the loader before loading entries to ensure + # there is no race where multiple entries try to load it + # at the same time. + await hass.async_add_executor_job(get_loader) + _async_register_events_and_services(hass) if DOMAIN not in config: @@ -516,6 +522,7 @@ class HomeKit: advertised_address=self._advertise_ip, async_zeroconf_instance=async_zeroconf_instance, zeroconf_server=f"{uuid}-hap.local.", + loader=get_loader(), ) # If we do not load the mac address will be wrong @@ -997,7 +1004,6 @@ class HomeKitPairingQRView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Retrieve the pairing QRCode image.""" - # pylint: disable=no-self-use if not request.query_string: raise Unauthorized() entry_id, secret = request.query_string.split("-") diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 1d06fa04a57..9d428573b5b 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -10,9 +10,9 @@ from pyhap.accessory_driver import AccessoryDriver from pyhap.const import CATEGORY_OTHER from pyhap.util import callback as pyhap_callback -from homeassistant.components import cover +from homeassistant.components.cover import CoverDeviceClass, CoverEntityFeature from homeassistant.components.media_player import MediaPlayerDeviceClass -from homeassistant.components.remote import SUPPORT_ACTIVITY +from homeassistant.components.remote import RemoteEntityFeature from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( ATTR_BATTERY_CHARGING, @@ -134,23 +134,24 @@ def get_accessory( # noqa: C901 device_class = state.attributes.get(ATTR_DEVICE_CLASS) if device_class in ( - cover.CoverDeviceClass.GARAGE, - cover.CoverDeviceClass.GATE, - ) and features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): + CoverDeviceClass.GARAGE, + CoverDeviceClass.GATE, + ) and features & (CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE): a_type = "GarageDoorOpener" elif ( - device_class == cover.CoverDeviceClass.WINDOW - and features & cover.SUPPORT_SET_POSITION + device_class == CoverDeviceClass.WINDOW + and features & CoverEntityFeature.SET_POSITION ): a_type = "Window" - elif features & cover.SUPPORT_SET_POSITION: + elif features & CoverEntityFeature.SET_POSITION: a_type = "WindowCovering" - elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): + elif features & (CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE): a_type = "WindowCoveringBasic" - elif features & cover.SUPPORT_SET_TILT_POSITION: + elif features & CoverEntityFeature.SET_TILT_POSITION: # WindowCovering and WindowCoveringBasic both support tilt # only WindowCovering can handle the covers that are missing - # SUPPORT_SET_POSITION, SUPPORT_OPEN, and SUPPORT_CLOSE + # CoverEntityFeature.SET_POSITION, CoverEntityFeature.OPEN, + # and CoverEntityFeature.CLOSE a_type = "WindowCovering" elif state.domain == "fan": @@ -214,7 +215,7 @@ def get_accessory( # noqa: C901 elif state.domain == "vacuum": a_type = "Vacuum" - elif state.domain == "remote" and features & SUPPORT_ACTIVITY: + elif state.domain == "remote" and features & RemoteEntityFeature.ACTIVITY: a_type = "ActivityRemote" elif state.domain in ( diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index b7a00bc3ade..5f142fdb0fe 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -467,7 +467,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): entity_filter = self.hk_options.get(CONF_FILTER, {}) entities = entity_filter.get(CONF_INCLUDE_ENTITIES, []) all_supported_entities = _async_get_matching_entities( - self.hass, domains, include_entity_category=True + self.hass, domains, include_entity_category=True, include_hidden=True ) # In accessory mode we can only have one default_value = next( @@ -508,7 +508,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): entities = entity_filter.get(CONF_INCLUDE_ENTITIES, []) all_supported_entities = _async_get_matching_entities( - self.hass, domains, include_entity_category=True + self.hass, domains, include_entity_category=True, include_hidden=True ) if not entities: entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, []) @@ -646,12 +646,13 @@ def _exclude_by_entity_registry( ent_reg: entity_registry.EntityRegistry, entity_id: str, include_entity_category: bool, + include_hidden: bool, ) -> bool: """Filter out hidden entities and ones with entity category (unless specified).""" return bool( (entry := ent_reg.async_get(entity_id)) and ( - entry.hidden_by is not None + (not include_hidden and entry.hidden_by is not None) or (not include_entity_category and entry.entity_category is not None) ) ) @@ -661,6 +662,7 @@ def _async_get_matching_entities( hass: HomeAssistant, domains: list[str] | None = None, include_entity_category: bool = False, + include_hidden: bool = False, ) -> dict[str, str]: """Fetch all entities or entities in the given domains.""" ent_reg = entity_registry.async_get(hass) @@ -671,7 +673,7 @@ def _async_get_matching_entities( key=lambda item: item.entity_id, ) if not _exclude_by_entity_registry( - ent_reg, state.entity_id, include_entity_category + ent_reg, state.entity_id, include_entity_category, include_hidden ) } diff --git a/homeassistant/components/homekit/translations/bg.json b/homeassistant/components/homekit/translations/bg.json index a7aa5bb792b..1a0ba552ae7 100644 --- a/homeassistant/components/homekit/translations/bg.json +++ b/homeassistant/components/homekit/translations/bg.json @@ -17,12 +17,6 @@ "entities": "\u041e\u0431\u0435\u043a\u0442\u0438" } }, - "include_exclude": { - "data": { - "entities": "\u041e\u0431\u0435\u043a\u0442\u0438", - "mode": "\u0420\u0435\u0436\u0438\u043c" - } - }, "init": { "data": { "mode": "\u0420\u0435\u0436\u0438\u043c" diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index f347268d90e..f1036cceb25 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -55,18 +55,9 @@ "description": "S'inclouran totes les entitats de \u201c{domains}\u201d tret que se'n seleccionin d'espec\u00edfiques.", "title": "Selecciona les entitats a incloure" }, - "include_exclude": { - "data": { - "entities": "Entitats", - "mode": "Mode" - }, - "description": "Tria les entitats a incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'inclouran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", - "title": "Selecciona les entitats a incloure" - }, "init": { "data": { "domains": "Dominis a incloure", - "include_domains": "Dominis a incloure", "include_exclude_mode": "Mode d'inclusi\u00f3", "mode": "Mode de HomeKit" }, diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json index 765b3c3c1b4..7103e1e3caa 100644 --- a/homeassistant/components/homekit/translations/cs.json +++ b/homeassistant/components/homekit/translations/cs.json @@ -28,18 +28,9 @@ "description": "Zkontrolujte v\u0161echny kamery, kter\u00e9 podporuj\u00ed nativn\u00ed streamy H.264. Pokud kamera nepodporuje stream H.264, syst\u00e9m video p\u0159ek\u00f3duje pro HomeKit na H.264. P\u0159ek\u00f3dov\u00e1n\u00ed vy\u017eaduje v\u00fdkonn\u00fd procesor a je pravd\u011bpodobn\u00e9, \u017ee nebude fungovat na po\u010d\u00edta\u010d\u00edch s jednou z\u00e1kladn\u00ed deskou.", "title": "Vyberte videokodek kamery." }, - "include_exclude": { - "data": { - "entities": "Entity", - "mode": "Re\u017eim" - }, - "description": "Vyberte entity, kter\u00e9 maj\u00ed b\u00fdt vystaveny. V re\u017eimu P\u0159\u00edslu\u0161enstv\u00ed je vystavena pouze jedna entita. V re\u017eimu Zahrnut\u00ed p\u0159emost\u011bn\u00ed budou vystaveny v\u0161echny entity v dom\u00e9n\u011b, pokud nebudou vybr\u00e1ny konkr\u00e9tn\u00ed entity. V re\u017eimu Vylou\u010den\u00ed p\u0159emost\u011bn\u00ed budou vystaveny v\u0161echny entity v dom\u00e9n\u011b krom\u011b vylou\u010den\u00fdch entit.", - "title": "Vyberte entity, kter\u00e9 chcete vystavit" - }, "init": { "data": { "domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", - "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", "mode": "Re\u017eim" }, "title": "Vyberte dom\u00e9ny, kter\u00e9 chcete vystavit." diff --git a/homeassistant/components/homekit/translations/de.json b/homeassistant/components/homekit/translations/de.json index 24f6fd84eb0..d9c07e4cf21 100644 --- a/homeassistant/components/homekit/translations/de.json +++ b/homeassistant/components/homekit/translations/de.json @@ -55,18 +55,9 @@ "description": "Alle \"{domains}\"-Entit\u00e4ten werden einbezogen, sofern nicht bestimmte Entit\u00e4ten ausgew\u00e4hlt werden.", "title": "W\u00e4hle die einzuschlie\u00dfenden Entit\u00e4ten aus" }, - "include_exclude": { - "data": { - "entities": "Entit\u00e4ten", - "mode": "Modus" - }, - "description": "W\u00e4hle die einzubeziehenden Entit\u00e4ten aus. Im Zubeh\u00f6rmodus wird nur eine einzelne Entit\u00e4t eingeschlossen. Im Bridge-Include-Modus werden alle Entit\u00e4ten in der Dom\u00e4ne eingeschlossen, sofern nicht bestimmte Entit\u00e4ten ausgew\u00e4hlt sind. Im Bridge-Exclude-Modus werden alle Entit\u00e4ten in der Dom\u00e4ne eingeschlossen, au\u00dfer den ausgeschlossenen Entit\u00e4ten. F\u00fcr eine optimale Leistung wird f\u00fcr jeden TV-Media-Player, jede aktivit\u00e4tsbasierte Fernbedienung, jedes Schloss und jede Kamera ein separates HomeKit-Zubeh\u00f6r erstellt.", - "title": "W\u00e4hle die Entit\u00e4ten aus, die aufgenommen werden sollen" - }, "init": { "data": { "domains": "Einzubeziehende Domains", - "include_domains": "Einzubeziehende Domains", "include_exclude_mode": "Inklusionsmodus", "mode": "HomeKit-Modus" }, diff --git a/homeassistant/components/homekit/translations/el.json b/homeassistant/components/homekit/translations/el.json index 632d9aecd76..370617bd5fd 100644 --- a/homeassistant/components/homekit/translations/el.json +++ b/homeassistant/components/homekit/translations/el.json @@ -55,18 +55,9 @@ "description": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \"{domains}\" \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd, \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b5\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bf\u03cd\u03bd \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2.", "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd" }, - "include_exclude": { - "data": { - "entities": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2", - "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1" - }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1, \u03c0\u03b5\u03c1\u03b9\u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 include bridge, \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c4\u03bf\u03bc\u03ad\u03b1, \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b5\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bf\u03cd\u03bd \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2. \u03a3\u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03b9\u03c3\u03bc\u03bf\u03cd \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1\u03c2, \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd \u03cc\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03c4\u03bf\u03bc\u03ad\u03b1 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03bf\u03c5\u03bd \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03b9\u03c3\u03c4\u03b5\u03af. \u0393\u03b9\u03b1 \u03ba\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b7 \u03b1\u03c0\u03cc\u03b4\u03bf\u03c3\u03b7, \u03b8\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03ad\u03bd\u03b1 \u03be\u03b5\u03c7\u03c9\u03c1\u03b9\u03c3\u03c4\u03cc \u03b1\u03be\u03b5\u03c3\u03bf\u03c5\u03ac\u03c1 HomeKit \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03c0\u03bf\u03bb\u03c5\u03bc\u03ad\u03c3\u03c9\u03bd tv, \u03c4\u03b7\u03bb\u03b5\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03bf \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03c4\u03b7 \u03b4\u03c1\u03b1\u03c3\u03c4\u03b7\u03c1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1, \u03ba\u03bb\u03b5\u03b9\u03b4\u03b1\u03c1\u03b9\u03ac \u03ba\u03b1\u03b9 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b1.", - "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd" - }, "init": { "data": { "domains": "\u03a4\u03bf\u03bc\u03b5\u03af\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd", - "include_domains": "\u03a4\u03bf\u03bc\u03b5\u03af\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03b9\u03bb\u03b7\u03c6\u03b8\u03bf\u03cd\u03bd", "include_exclude_mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bc\u03c0\u03b5\u03c1\u03af\u03bb\u03b7\u03c8\u03b7\u03c2", "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 HomeKit" }, diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 72c6ba9df36..14585b60f1e 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -55,18 +55,9 @@ "description": "All \u201c{domains}\u201d entities will be included unless specific entities are selected.", "title": "Select the entities to be included" }, - "include_exclude": { - "data": { - "entities": "Entities", - "mode": "Mode" - }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a separate HomeKit accessory will be created for each tv media player, activity based remote, lock, and camera.", - "title": "Select entities to be included" - }, "init": { "data": { "domains": "Domains to include", - "include_domains": "Domains to include", "include_exclude_mode": "Inclusion Mode", "mode": "HomeKit Mode" }, diff --git a/homeassistant/components/homekit/translations/es-419.json b/homeassistant/components/homekit/translations/es-419.json index 2b670f13c7e..459832cac80 100644 --- a/homeassistant/components/homekit/translations/es-419.json +++ b/homeassistant/components/homekit/translations/es-419.json @@ -34,9 +34,6 @@ "title": "Seleccione el c\u00f3dec de video de la c\u00e1mara." }, "init": { - "data": { - "include_domains": "Dominios para incluir" - }, "description": "Las entidades en los \"Dominios para incluir\" se vincular\u00e1n a HomeKit. Podr\u00e1 seleccionar qu\u00e9 entidades excluir de esta lista en la siguiente pantalla.", "title": "Seleccione dominios para puentear." }, diff --git a/homeassistant/components/homekit/translations/es.json b/homeassistant/components/homekit/translations/es.json index cc1925b8095..ef60610a8a1 100644 --- a/homeassistant/components/homekit/translations/es.json +++ b/homeassistant/components/homekit/translations/es.json @@ -40,17 +40,8 @@ "entities": "Entidades" } }, - "include_exclude": { - "data": { - "entities": "Entidades", - "mode": "Modo" - }, - "description": "Selecciona las entidades que deseas exponer. En el modo de accesorio, solo se expone una \u00fanica entidad. En el modo de inclusi\u00f3n de puente, todas las entidades del dominio se expondr\u00e1n a menos que se seleccionen entidades espec\u00edficas. En el modo de exclusi\u00f3n de puentes, todas las entidades del dominio se expondr\u00e1n excepto las entidades excluidas.", - "title": "Seleccionar entidades para exponer" - }, "init": { "data": { - "include_domains": "Dominios para incluir", "mode": "Modo" }, "description": "Las entidades de los \"Dominios que se van a incluir\" se establecer\u00e1n en HomeKit. Podr\u00e1 seleccionar qu\u00e9 entidades excluir de esta lista en la siguiente pantalla.", diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index d0ef2e79f03..671dede46e0 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -55,18 +55,9 @@ "description": "Kaasatakse k\u00f5ik olemid {domains}, v\u00e4lja arvatud juhul, kui valitud on konkreetsed olemid.", "title": "Vali kaasatavad olemid" }, - "include_exclude": { - "data": { - "entities": "Olemid", - "mode": "Re\u017eiim" - }, - "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga TV meediumim\u00e4ngija, luku, juhtpuldi ja kaamera jaoks.", - "title": "Vali kaasatavd olemid" - }, "init": { "data": { "domains": "Kaasa domeenid", - "include_domains": "Kaasatud domeenid", "include_exclude_mode": "Kaasamise re\u017eiim", "mode": "HomeKiti re\u017eiim" }, diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index 1977f7e6c18..e4850893bd3 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -55,18 +55,9 @@ "description": "Toutes les entit\u00e9s \"{domaines}\" seront incluses, sauf si des entit\u00e9s sp\u00e9cifiques sont s\u00e9lectionn\u00e9es.", "title": "S\u00e9lectionnez les entit\u00e9s \u00e0 inclure" }, - "include_exclude": { - "data": { - "entities": "Entit\u00e9s", - "mode": "Mode" - }, - "description": "Choisissez les entit\u00e9s \u00e0 inclure. En mode accessoire, une seule entit\u00e9 est incluse. En mode d'inclusion de pont, toutes les entit\u00e9s du domaine seront incluses \u00e0 moins que des entit\u00e9s sp\u00e9cifiques ne soient s\u00e9lectionn\u00e9es. En mode d'exclusion de pont, toutes les entit\u00e9s du domaine seront incluses \u00e0 l'exception des entit\u00e9s exclues. Pour de meilleures performances, un accessoire HomeKit distinct sera cr\u00e9\u00e9 pour chaque lecteur multim\u00e9dia TV et cam\u00e9ra.", - "title": "S\u00e9lectionnez les entit\u00e9s \u00e0 exposer" - }, "init": { "data": { "domains": "Domaines \u00e0 inclure", - "include_domains": "Domaines \u00e0 inclure", "include_exclude_mode": "Mode d'inclusion", "mode": "Mode HomeKit" }, diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index 22f3515b497..defce6a05a2 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -28,17 +28,10 @@ "entities": "\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea" } }, - "include_exclude": { - "data": { - "mode": "\u05de\u05e6\u05d1" - }, - "title": "\u05d1\u05d7\u05e8 \u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05e9\u05d9\u05d9\u05db\u05dc\u05dc\u05d5" - }, "init": { "data": { "domains": "\u05ea\u05d7\u05d5\u05de\u05d9\u05dd \u05e9\u05d9\u05e9 \u05dc\u05db\u05dc\u05d5\u05dc", - "include_domains": "\u05ea\u05d7\u05d5\u05de\u05d9\u05dd \u05e9\u05d9\u05e9 \u05dc\u05db\u05dc\u05d5\u05dc", - "mode": "\u05de\u05e6\u05d1" + "mode": "\u05de\u05e6\u05d1 HomeKit" } }, "yaml": { diff --git a/homeassistant/components/homekit/translations/hu.json b/homeassistant/components/homekit/translations/hu.json index 7c3a28abfd4..a1c64f6fac3 100644 --- a/homeassistant/components/homekit/translations/hu.json +++ b/homeassistant/components/homekit/translations/hu.json @@ -55,23 +55,14 @@ "description": "Az \u00f6sszes \"{domains}\" entit\u00e1s beker\u00fcl, kiv\u00e9ve, ha konkr\u00e9t entit\u00e1sok vannak kijel\u00f6lve.", "title": "V\u00e1lassza ki a felvenni k\u00edv\u00e1nt entit\u00e1sokat" }, - "include_exclude": { - "data": { - "entities": "Entit\u00e1sok", - "mode": "M\u00f3d" - }, - "description": "V\u00e1lassza ki a felvenni k\u00edv\u00e1nt entit\u00e1sokat. Kieg\u00e9sz\u00edt\u0151 m\u00f3dban csak egyetlen entit\u00e1s szerepel. H\u00eddbefogad\u00e1si m\u00f3dban a tartom\u00e1ny \u00f6sszes entit\u00e1sa szerepelni fog, hacsak nincsenek kijel\u00f6lve konkr\u00e9t entit\u00e1sok. H\u00eddkiz\u00e1r\u00e1si m\u00f3dban a domain \u00f6sszes entit\u00e1sa szerepelni fog, kiv\u00e9ve a kiz\u00e1rt entit\u00e1sokat. A legjobb teljes\u00edtm\u00e9ny \u00e9rdek\u00e9ben minden TV m\u00e9dialej\u00e1tsz\u00f3hoz, tev\u00e9kenys\u00e9galap\u00fa t\u00e1vir\u00e1ny\u00edt\u00f3hoz, z\u00e1rhoz \u00e9s f\u00e9nyk\u00e9pez\u0151g\u00e9phez k\u00fcl\u00f6n HomeKit tartoz\u00e9kot hoznak l\u00e9tre.", - "title": "V\u00e1lassza ki a felvenni k\u00edv\u00e1nt entit\u00e1sokat" - }, "init": { "data": { "domains": "Felvenni k\u00edv\u00e1nt domainek", - "include_domains": "Felvenni k\u00edv\u00e1nt domainek", "include_exclude_mode": "Felv\u00e9tel m\u00f3dja", "mode": "HomeKit m\u00f3d" }, "description": "A HomeKit konfigur\u00e1lhat\u00f3 \u00fagy, hogy egy h\u00edd vagy egyetlen tartoz\u00e9k l\u00e1that\u00f3 legyen. Kieg\u00e9sz\u00edt\u0151 m\u00f3dban csak egyetlen entit\u00e1s haszn\u00e1lhat\u00f3. A tartoz\u00e9k m\u00f3dra van sz\u00fcks\u00e9g ahhoz, hogy a TV -eszk\u00f6zoszt\u00e1ly\u00fa m\u00e9dialej\u00e1tsz\u00f3k megfelel\u0151en m\u0171k\u00f6djenek. A \u201eTartalmazand\u00f3 tartom\u00e1nyok\u201d entit\u00e1sai szerepelni fognak a HomeKitben. A k\u00f6vetkez\u0151 k\u00e9perny\u0151n kiv\u00e1laszthatja, hogy mely entit\u00e1sokat k\u00edv\u00e1nja felvenni vagy kiz\u00e1rni a list\u00e1b\u00f3l.", - "title": "V\u00e1lassza ki a felvenni k\u00edv\u00e1nt domaineket." + "title": "V\u00e1lassza ki a m\u00f3dot \u00e9s a tartom\u00e1nyokat." }, "yaml": { "description": "Ez a bejegyz\u00e9s YAML-en kereszt\u00fcl vez\u00e9relhet\u0151", diff --git a/homeassistant/components/homekit/translations/id.json b/homeassistant/components/homekit/translations/id.json index 5faedff893b..8293ad9d72c 100644 --- a/homeassistant/components/homekit/translations/id.json +++ b/homeassistant/components/homekit/translations/id.json @@ -55,18 +55,9 @@ "description": "Semua entitas \"{domains}\" akan disertakan kecuali entitas tertentu dipilih.", "title": "Pilih entitas yang akan disertakan" }, - "include_exclude": { - "data": { - "entities": "Entitas", - "mode": "Mode" - }, - "description": "Pilih entitas yang akan disertakan. Dalam mode aksesori, hanya satu entitas yang disertakan. Dalam mode \"bridge include\", semua entitas di domain akan disertakan, kecuali entitas tertentu dipilih. Dalam mode \"bridge exclude\", semua entitas di domain akan disertakan, kecuali untuk entitas tertentu yang dipilih. Untuk kinerja terbaik, aksesori HomeKit terpisah diperlukan untuk masing-masing pemutar media TV, remote berbasis aktivitas, kunci, dan kamera.", - "title": "Pilih entitas untuk disertakan" - }, "init": { "data": { "domains": "Domain yang disertakan", - "include_domains": "Domain yang disertakan", "include_exclude_mode": "Mode Penyertaan", "mode": "Mode HomeKit" }, diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 186bf989a4e..7acec1af5c3 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -55,18 +55,9 @@ "description": "Tutte le entit\u00e0 \"{domini}\" saranno incluse a eccezione delle entit\u00e0 escluse e delle entit\u00e0 categorizzate.", "title": "Seleziona le entit\u00e0 da includere" }, - "include_exclude": { - "data": { - "entities": "Entit\u00e0", - "mode": "Modalit\u00e0" - }, - "description": "Scegli le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, sar\u00e0 creata una HomeKit separata accessoria per ogni lettore multimediale TV, telecomando basato sulle attivit\u00e0, serratura e videocamera.", - "title": "Seleziona le entit\u00e0 da includere" - }, "init": { "data": { "domains": "Domini da includere", - "include_domains": "Domini da includere", "include_exclude_mode": "Modalit\u00e0 di inclusione", "mode": "Modalit\u00e0 HomeKit" }, diff --git a/homeassistant/components/homekit/translations/ja.json b/homeassistant/components/homekit/translations/ja.json index 6d6b441484f..6bcad37ad61 100644 --- a/homeassistant/components/homekit/translations/ja.json +++ b/homeassistant/components/homekit/translations/ja.json @@ -55,18 +55,9 @@ "description": "\u7279\u5b9a\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u306a\u3044\u9650\u308a\u3001\u3059\u3079\u3066\u306e \u201c{domains}\u201d \u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u542b\u307e\u308c\u307e\u3059\u3002", "title": "\u542b\u3081\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9078\u629e" }, - "include_exclude": { - "data": { - "entities": "\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", - "mode": "\u30e2\u30fc\u30c9" - }, - "description": "\u542b\u307e\u308c\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9078\u629e\u3057\u307e\u3059\u3002\u30a2\u30af\u30bb\u30b5\u30ea \u30e2\u30fc\u30c9\u3067\u306f\u30011\u3064\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u306e\u307f\u304c\u542b\u307e\u308c\u307e\u3059\u3002\u30d6\u30ea\u30c3\u30b8\u30a4\u30f3\u30af\u30eb\u30fc\u30c9\u30e2\u30fc\u30c9\u3067\u306f\u3001\u7279\u5b9a\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u306a\u3044\u9650\u308a\u3001\u30c9\u30e1\u30a4\u30f3\u5185\u306e\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u542b\u307e\u308c\u307e\u3059\u3002\u30d6\u30ea\u30c3\u30b8\u9664\u5916\u30e2\u30fc\u30c9\u3067\u306f\u3001\u9664\u5916\u3055\u308c\u305f\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9664\u3044\u3066\u3001\u30c9\u30e1\u30a4\u30f3\u5185\u306e\u3059\u3079\u3066\u306e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u542b\u307e\u308c\u307e\u3059\u3002\u6700\u9ad8\u306e\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u3092\u5b9f\u73fe\u3059\u308b\u305f\u3081\u306b\u3001\u30c6\u30ec\u30d3\u30e1\u30c7\u30a3\u30a2\u30d7\u30ec\u30fc\u30e4\u30fc\u3001\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3\u30d9\u30fc\u30b9\u306e\u30ea\u30e2\u30b3\u30f3(remote)\u3001\u30ed\u30c3\u30af\u3001\u30ab\u30e1\u30e9\u306b\u5bfe\u3057\u3066\u500b\u5225\u306b\u3001HomeKit\u30a2\u30af\u30bb\u30b5\u30ea\u3092\u4f5c\u6210\u3057\u307e\u3059\u3002", - "title": "\u542b\u3081\u308b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u9078\u629e" - }, "init": { "data": { "domains": "\u542b\u3081\u308b\u30c9\u30e1\u30a4\u30f3", - "include_domains": "\u542b\u3081\u308b\u30c9\u30e1\u30a4\u30f3", "include_exclude_mode": "\u30a4\u30f3\u30af\u30eb\u30fc\u30b8\u30e7\u30f3\u30e2\u30fc\u30c9", "mode": "\u30e2\u30fc\u30c9" }, diff --git a/homeassistant/components/homekit/translations/ka.json b/homeassistant/components/homekit/translations/ka.json index 97787f722fc..63b99f73416 100644 --- a/homeassistant/components/homekit/translations/ka.json +++ b/homeassistant/components/homekit/translations/ka.json @@ -1,14 +1,6 @@ { "options": { "step": { - "include_exclude": { - "data": { - "entities": "\u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d4\u10d1\u10d8", - "mode": "\u10e0\u10d4\u10df\u10db\u10d8" - }, - "description": "\u10e8\u10d4\u10d0\u10e0\u10e9\u10d8\u10d4\u10d7 \u10d2\u10d0\u10db\u10dd\u10e1\u10d0\u10d5\u10da\u10d4\u10dc\u10d8 \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d4\u10d1\u10d8. \u10d0\u10e5\u10e1\u10d4\u10e1\u10e3\u10d0\u10e0\u10d4\u10d1\u10d8\u10e1 \u10e0\u10d4\u10df\u10d8\u10db\u10e8\u10d8 \u10d2\u10d0\u10db\u10dd\u10d5\u10da\u10d4\u10dc\u10d0\u10e1 \u10d4\u10e5\u10d5\u10d4\u10db\u10d3\u10d4\u10d1\u10d0\u10e0\u10d4\u10d1\u10d0 \u10db\u10ee\u10dd\u10da\u10dd\u10d3 \u10d4\u10e0\u10d7\u10d8 \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d8. \u10d1\u10e0\u10d8\u10ef\u10d8\u10e1 \u10db\u10dd\u10ea\u10e3\u10da\u10dd\u10d1\u10d8\u10e1 \u10e0\u10d4\u10df\u10d8\u10db\u10e8\u10d8 \u10d7\u10e3 \u10e1\u10de\u10d4\u10ea\u10d8\u10e4\u10d8\u10d9\u10e3\u10e0\u10d8 \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d4\u10d1\u10d8 \u10d0\u10e0 \u10d0\u10e0\u10d8\u10e1 \u10e8\u10d4\u10e0\u10e9\u10d4\u10e3\u10da\u10d8, \u10db\u10d8\u10e1\u10d0\u10ec\u10d5\u10d3\u10dd\u10db\u10d8 \u10d8\u10e5\u10dc\u10d4\u10d1\u10d0 \u10d3\u10dd\u10db\u10d4\u10dc\u10d8\u10e1 \u10e7\u10d5\u10d4\u10da\u10d0 \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d8. \u10ee\u10d8\u10d3\u10d8\u10e1 \u10d2\u10d0\u10db\u10dd\u10e0\u10d8\u10ea\u10ee\u10d5\u10d8\u10e1 \u10e0\u10d4\u10df\u10d8\u10db\u10e8\u10d8, \u10d3\u10dd\u10db\u10d4\u10dc\u10d8\u10e1 \u10e7\u10d5\u10d4\u10da\u10d0 \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d8 \u10d8\u10e5\u10dc\u10d4\u10d1\u10d0 \u10dc\u10d8\u10e1\u10d0\u10ec\u10d5\u10d3\u10dd\u10db\u10d8 \u10d2\u10d0\u10db\u10dd\u10e0\u10d8\u10ea\u10ee\u10e3\u10da \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d4\u10d1\u10d8\u10e1 \u10d2\u10d0\u10e0\u10d3\u10d0.", - "title": "\u10d0\u10d8\u10e0\u10e9\u10d8\u10d4\u10d7 \u10d2\u10d0\u10db\u10dd\u10e1\u10d0\u10d5\u10da\u10d4\u10dc\u10d8 \u10dd\u10d1\u10d8\u10d4\u10e5\u10e2\u10d4\u10d1\u10d8" - }, "init": { "data": { "mode": "\u10e0\u10d4\u10df\u10d8\u10db\u10d8" diff --git a/homeassistant/components/homekit/translations/ko.json b/homeassistant/components/homekit/translations/ko.json index e93a55db971..cd8cbb81943 100644 --- a/homeassistant/components/homekit/translations/ko.json +++ b/homeassistant/components/homekit/translations/ko.json @@ -33,17 +33,8 @@ "description": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \ubaa8\ub4e0 \uce74\uba54\ub77c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \uce74\uba54\ub77c\uac00 H.264 \uc2a4\ud2b8\ub9bc\uc744 \ucd9c\ub825\ud558\uc9c0 \uc54a\uc73c\uba74 \uc2dc\uc2a4\ud15c\uc740 \ube44\ub514\uc624\ub97c HomeKit\uc6a9 H.264 \ud3ec\ub9f7\uc73c\ub85c \ubcc0\ud658\uc2dc\ud0b5\ub2c8\ub2e4. \ud2b8\ub79c\uc2a4\ucf54\ub529 \ubcc0\ud658\uc5d0\ub294 \ub192\uc740 CPU \uc131\ub2a5\uc774 \ud544\uc694\ud558\uba70 Raspberry Pi\uc640 \uac19\uc740 \ub2e8\uc77c \ubcf4\ub4dc \ucef4\ud4e8\ud130\uc5d0\uc11c\ub294 \uc791\ub3d9\ud558\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\uce74\uba54\ub77c \ube44\ub514\uc624 \ucf54\ub371 \uc120\ud0dd\ud558\uae30" }, - "include_exclude": { - "data": { - "entities": "\uad6c\uc131\uc694\uc18c", - "mode": "\ubaa8\ub4dc" - }, - "description": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \ud3ec\ud568\ub429\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \ud3ec\ud568 \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ud2b9\uc815 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud558\uc9c0 \uc54a\ub294 \ud55c \ub3c4\uba54\uc778\uc758 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \uc81c\uc678 \ubaa8\ub4dc\uc5d0\uc11c\ub294 \uc81c\uc678\ub41c \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uc678\ud55c \ub3c4\uba54\uc778\uc758 \ub098\uba38\uc9c0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ucd5c\uc0c1\uc758 \uc131\ub2a5\uc744 \uc704\ud574 \uac01 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \ud65c\ub3d9 \uae30\ubc18 \ub9ac\ubaa8\ucf58, \uc7a0\uae08\uae30\uae30, \uce74\uba54\ub77c\ub294 \ubcc4\ub3c4\uc758 HomeKit \uc561\uc138\uc11c\ub9ac\ub85c \uc0dd\uc131\ub429\ub2c8\ub2e4.", - "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" - }, "init": { "data": { - "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", "mode": "\ubaa8\ub4dc" }, "description": "\ube0c\ub9ac\uc9c0 \ub610\ub294 \ub2e8\uc77c \uc561\uc138\uc11c\ub9ac\ub97c \ub178\ucd9c\ud558\uc5ec HomeKit\ub97c \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. TV \uae30\uae30 \ud074\ub798\uc2a4\uac00 \uc788\ub294 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uac00 \uc81c\ub300\ub85c \uc791\ub3d9\ud558\ub824\uba74 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\uac00 HomeKit\uc5d0 \ud3ec\ud568\ub418\uba70, \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc5d0 \ud3ec\ud568\ud558\uac70\ub098 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/homekit/translations/lb.json b/homeassistant/components/homekit/translations/lb.json index 6261d4b5ed4..409d860940d 100644 --- a/homeassistant/components/homekit/translations/lb.json +++ b/homeassistant/components/homekit/translations/lb.json @@ -33,17 +33,8 @@ "description": "Iwwerpr\u00e9if all Kamera op nativ H.264 \u00cbnnerst\u00ebtzung. Falls d'Kamera keng H.264 Stream Ausgab huet, transkod\u00e9iert de System de Video op H.264 fir Homekit. Transkod\u00e9iere ben\u00e9idegt eng performant CPU an w\u00e4ert net anst\u00e4nneg op Single Board Computer funktion\u00e9ieren.", "title": "Kamera Video Codec auswielen." }, - "include_exclude": { - "data": { - "entities": "Entit\u00e9iten", - "mode": "Modus" - }, - "description": "Entit\u00e9iten auswielen d\u00e9i expos\u00e9iert solle ginn. Am Acessoire Modus g\u00ebtt n\u00ebmmen eng eenzeg Entit\u00e9it expos\u00e9iert. Am Bridge include Modus, ginn all Entit\u00e9iten vum Domaine expos\u00e9iert ausser spezifesch ausgewielten Entit\u00e9iten. Am Bride Exclude Modus, ginn all Entit\u00e9ite vum Domaine expos\u00e9iert ausser d\u00e9i ausgeschlossen Entit\u00e9iten.", - "title": "Entit\u00e9iten auswielen d\u00e9i expos\u00e9iert solle ginn" - }, "init": { "data": { - "include_domains": "Domaine d\u00e9i solle ageschloss ginn", "mode": "Modus" }, "description": "HomeKit kann entweder als Bridge oder Single Accessoire konfigur\u00e9iert ginn. Am Acessoire Modus ka n\u00ebmmen eng eenzeg Entit\u00e9it benotzt ginn. D\u00ebse Modus ass n\u00e9ideg fir de korretke Betrib vu Medie Spiller vun der TV Klasse. Entit\u00e9ite an der \"Domaine fir z'expos\u00e9ieren\" ginn mat HomeKit expos\u00e9iert. Du kanns am n\u00e4chste Schr\u00ebtt auswielen w\u00e9ieng Entit\u00e9ite aus oder ageschloss solle ginn.", diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 2e16ffde8ef..7f8ab1e2ff8 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -55,18 +55,9 @@ "description": "Alle \" {domains} \"-entiteiten zullen worden opgenomen, tenzij specifieke entiteiten zijn geselecteerd.", "title": "Selecteer de entiteiten die moeten worden opgenomen" }, - "include_exclude": { - "data": { - "entities": "Entiteiten", - "mode": "Mode" - }, - "description": "Kies de entiteiten die u wilt opnemen. In de accessoiremodus is slechts een enkele entiteit inbegrepen. In de bridge-include-modus worden alle entiteiten in het domein opgenomen, tenzij specifieke entiteiten zijn geselecteerd. In de bridge-uitsluitingsmodus worden alle entiteiten in het domein opgenomen, behalve de uitgesloten entiteiten. Voor de beste prestaties wordt voor elke media speler, activiteit gebaseerde afstandsbediening, slot en camera een afzonderlijke Homekit-accessoire gemaakt.", - "title": "Selecteer de entiteiten die u wilt opnemen" - }, "init": { "data": { "domains": "Domeinen om op te nemen", - "include_domains": "Domeinen om op te nemen", "include_exclude_mode": "Inclusiemodus", "mode": "HomeKit-modus" }, diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 88e39d0835e..29f775853fd 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -55,18 +55,9 @@ "description": "Alle \" {domains} \"-enheter vil bli inkludert med mindre spesifikke enheter er valgt.", "title": "Velg enhetene som skal inkluderes" }, - "include_exclude": { - "data": { - "entities": "Entiteter", - "mode": "Modus" - }, - "description": "Velg entitetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt entitet inkludert. I bridge-inkluderingsmodus vil alle entiteter i domenet bli inkludert, med mindre spesifikke entiteter er valgt. I bridge-ekskluderingsmodus vil alle entiteter i domenet bli inkludert, bortsett fra de ekskluderte entitetene. For best ytelse opprettes et eget HomeKit-tilbeh\u00f8r for hver tv-mediaspiller, aktivitetsbasert fjernkontroll, l\u00e5s og kamera.", - "title": "Velg entiteter som skal inkluderes" - }, "init": { "data": { "domains": "Domener \u00e5 inkludere", - "include_domains": "Domener \u00e5 inkludere", "include_exclude_mode": "Inkluderingsmodus", "mode": "HomeKit-modus" }, diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 745f07bdadb..73ed271d636 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -55,18 +55,9 @@ "description": "Wszystkie encje \"{domains}\" zostan\u0105 uwzgl\u0119dnione, chyba \u017ce zostan\u0105 wybrane konkretne encje.", "title": "Wybierz encje, kt\u00f3re maj\u0105 zosta\u0107 uwzgl\u0119dnione" }, - "include_exclude": { - "data": { - "entities": "Encje", - "mode": "Tryb" - }, - "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci, zostanie utworzone oddzielne akcesorium HomeKit dla ka\u017cdego tv media playera, aktywno\u015bci pilota, zamka oraz kamery.", - "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione" - }, "init": { "data": { "domains": "Domeny do uwzgl\u0119dnienia", - "include_domains": "Domeny do uwzgl\u0119dnienia", "include_exclude_mode": "Tryb dodawania", "mode": "Tryb HomeKit" }, diff --git a/homeassistant/components/homekit/translations/pt-BR.json b/homeassistant/components/homekit/translations/pt-BR.json index ad8aab120f4..5fcc2748d0c 100644 --- a/homeassistant/components/homekit/translations/pt-BR.json +++ b/homeassistant/components/homekit/translations/pt-BR.json @@ -55,18 +55,9 @@ "description": "Todas as entidades \"{domains}\" ser\u00e3o inclu\u00eddas, a menos que entidades espec\u00edficas sejam selecionadas.", "title": "Selecione as entidades a serem inclu\u00eddas" }, - "include_exclude": { - "data": { - "entities": "Entidades", - "mode": "Modo" - }, - "description": "Escolha as entidades a serem inclu\u00eddas. No modo acess\u00f3rio, apenas uma \u00fanica entidade est\u00e1 inclu\u00edda. No modo de incluir ponte, todas as entidades do dom\u00ednio ser\u00e3o inclu\u00eddas a menos que entidades espec\u00edficas sejam selecionadas. No modo de exclus\u00e3o da ponte, todas as entidades do dom\u00ednio ser\u00e3o inclu\u00eddas, exceto para as entidades exclu\u00eddas. Para melhor desempenho, um acess\u00f3rio HomeKit separado ser\u00e1 criado para cada leitor de m\u00eddia de TV, controle remoto, bloqueio e c\u00e2mera baseados em atividades.", - "title": "Selecione as entidades a serem inclu\u00eddas" - }, "init": { "data": { - "domains": "Dom\u00ednios a serem inclu\u00eddos", - "include_domains": "Dom\u00ednios para incluir", + "domains": "Dom\u00ednios para incluir", "include_exclude_mode": "Modo de inclus\u00e3o", "mode": "Modo HomeKit" }, diff --git a/homeassistant/components/homekit/translations/pt.json b/homeassistant/components/homekit/translations/pt.json index 920beaa98df..3df84b70866 100644 --- a/homeassistant/components/homekit/translations/pt.json +++ b/homeassistant/components/homekit/translations/pt.json @@ -23,16 +23,8 @@ "cameras": { "title": "Selecione o codec de v\u00eddeo da c\u00e2mera." }, - "include_exclude": { - "data": { - "entities": "Entidades", - "mode": "Modo" - }, - "title": "Selecione as entidades a serem expostas" - }, "init": { "data": { - "include_domains": "Dom\u00ednios a incluir", "mode": "Modo" }, "title": "Selecione os dom\u00ednios a serem expostos." diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 6de8ed572c0..81dd9afa302 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -55,18 +55,9 @@ "description": "\u0411\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u201c{domains}\u201c, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b.", "title": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432 \u0441\u043f\u0438\u0441\u043e\u043a" }, - "include_exclude": { - "data": { - "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", - "mode": "\u0420\u0435\u0436\u0438\u043c" - }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u044b, \u043f\u0443\u043b\u044c\u0442\u044b \u0414\u0423 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439, \u0437\u0430\u043c\u043a\u0438 \u0438 \u043a\u0430\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430.", - "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" - }, "init": { "data": { "domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b", - "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b", "include_exclude_mode": "\u0420\u0435\u0436\u0438\u043c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "mode": "\u0420\u0435\u0436\u0438\u043c HomeKit" }, diff --git a/homeassistant/components/homekit/translations/sl.json b/homeassistant/components/homekit/translations/sl.json index dcfc2496011..333ec699253 100644 --- a/homeassistant/components/homekit/translations/sl.json +++ b/homeassistant/components/homekit/translations/sl.json @@ -32,17 +32,7 @@ }, "title": "Izberite video kodek kamere." }, - "include_exclude": { - "data": { - "entities": "Entitete", - "mode": "Na\u010din" - }, - "title": "Izberite entitete, ki jih \u017eelite izpostaviti" - }, "init": { - "data": { - "include_domains": "Domene za vklju\u010ditev" - }, "description": "Subjekti v domenah, ki jih \u017eelite vklju\u010diti, bodo prevezani na HomeKit. Na naslednjem zaslonu boste lahko izbrali subjekte, ki jih \u017eelite izklju\u010diti s tega seznama.", "title": "Izberite domene za premostitev." }, diff --git a/homeassistant/components/homekit/translations/sv.json b/homeassistant/components/homekit/translations/sv.json index 0bc23c456ff..be1e79453e8 100644 --- a/homeassistant/components/homekit/translations/sv.json +++ b/homeassistant/components/homekit/translations/sv.json @@ -19,9 +19,6 @@ "title": "V\u00e4lj kamerans videoavkodare." }, "init": { - "data": { - "include_domains": "Dom\u00e4ner att inkludera" - }, "title": "V\u00e4lj dom\u00e4ner som ska inkluderas." } } diff --git a/homeassistant/components/homekit/translations/tr.json b/homeassistant/components/homekit/translations/tr.json index 49d4a2cf1fa..52683302a34 100644 --- a/homeassistant/components/homekit/translations/tr.json +++ b/homeassistant/components/homekit/translations/tr.json @@ -55,18 +55,9 @@ "description": "Belirli varl\u0131klar se\u00e7ilmedi\u011fi s\u00fcrece t\u00fcm \u201c {domains} \u201d varl\u0131klar\u0131 dahil edilecektir.", "title": "Dahil edilecek varl\u0131klar\u0131 se\u00e7in" }, - "include_exclude": { - "data": { - "entities": "Varl\u0131klar", - "mode": "Mod" - }, - "description": "Dahil edilecek varl\u0131klar\u0131 se\u00e7in. Aksesuar modunda yaln\u0131zca tek bir varl\u0131k dahil edilir. K\u00f6pr\u00fc dahil modunda, belirli varl\u0131klar se\u00e7ilmedi\u011fi s\u00fcrece etki alan\u0131ndaki t\u00fcm varl\u0131klar dahil edilecektir. K\u00f6pr\u00fc hari\u00e7 tutma modunda, hari\u00e7 tutulan varl\u0131klar d\u0131\u015f\u0131nda etki alan\u0131ndaki t\u00fcm varl\u0131klar dahil edilecektir. En iyi performans i\u00e7in, her TV medya oynat\u0131c\u0131, aktivite tabanl\u0131 uzaktan kumanda, kilit ve kamera i\u00e7in ayr\u0131 bir HomeKit aksesuar\u0131 olu\u015fturulacakt\u0131r.", - "title": "Dahil edilecek varl\u0131klar\u0131 se\u00e7in" - }, "init": { "data": { "domains": "\u0130\u00e7erecek etki alanlar\u0131", - "include_domains": "\u0130\u00e7erecek etki alanlar\u0131", "include_exclude_mode": "Ekleme Modu", "mode": "HomeKit Modu" }, diff --git a/homeassistant/components/homekit/translations/uk.json b/homeassistant/components/homekit/translations/uk.json index 0210380ad38..52da83cca73 100644 --- a/homeassistant/components/homekit/translations/uk.json +++ b/homeassistant/components/homekit/translations/uk.json @@ -33,17 +33,8 @@ "description": "\u042f\u043a\u0449\u043e \u043a\u0430\u043c\u0435\u0440\u0430 \u043d\u0435 \u0432\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u0442\u0456\u043a H.264, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u043e\u0432\u0443\u0454 \u0432\u0456\u0434\u0435\u043e \u0432 H.264 \u0434\u043b\u044f HomeKit. \u0422\u0440\u0430\u043d\u0441\u043a\u043e\u0434\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u0432\u0438\u0441\u043e\u043a\u043e\u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u043e\u0440\u0430 \u0456 \u043d\u0430\u0432\u0440\u044f\u0434 \u0447\u0438 \u0431\u0443\u0434\u0435 \u043f\u0440\u0430\u0446\u044e\u0432\u0430\u0442\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043f\u043b\u0430\u0442\u043d\u0438\u0445 \u043a\u043e\u043c\u043f'\u044e\u0442\u0435\u0440\u0430\u0445.", "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0456\u0434\u0435\u043e\u043a\u043e\u0434\u0435\u043a \u043a\u0430\u043c\u0435\u0440\u0438." }, - "include_exclude": { - "data": { - "entities": "\u0421\u0443\u0442\u043d\u043e\u0441\u0442\u0456", - "mode": "\u0420\u0435\u0436\u0438\u043c" - }, - "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432\u0441\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0443, \u044f\u043a\u0449\u043e \u043d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u0456 \u043f\u0435\u0432\u043d\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432\u0441\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u0456\u043c \u0432\u0438\u0431\u0440\u0430\u043d\u0438\u0445.", - "title": "\u0412\u0438\u0431\u0456\u0440 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit" - }, "init": { "data": { - "include_domains": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u0438", "mode": "\u0420\u0435\u0436\u0438\u043c" }, "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 HomeKit \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u043c\u043e\u0441\u0442\u0430 \u0430\u0431\u043e \u044f\u043a \u043e\u043a\u0440\u0435\u043c\u0438\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0434\u0438\u043d \u043e\u0431'\u0454\u043a\u0442. \u041c\u0435\u0434\u0456\u0430\u043f\u043b\u0435\u0454\u0440\u0438, \u044f\u043a\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u0432 Home Assistant \u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c 'device_class: tv', \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0457 \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u043e\u0432\u0430\u043d\u0456 \u0432 Homekit \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430. \u041e\u0431'\u0454\u043a\u0442\u0438, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u043d\u0438\u043c \u0434\u043e\u043c\u0435\u043d\u0430\u043c, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432 HomeKit. \u041d\u0430 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u043c\u0443 \u0435\u0442\u0430\u043f\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0412\u0438 \u0437\u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u0431\u0440\u0430\u0442\u0438, \u044f\u043a\u0456 \u043e\u0431'\u0454\u043a\u0442\u0438 \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0437 \u0446\u0438\u0445 \u0434\u043e\u043c\u0435\u043d\u0456\u0432.", diff --git a/homeassistant/components/homekit/translations/zh-Hans.json b/homeassistant/components/homekit/translations/zh-Hans.json index 3f6d005f045..852979fb12a 100644 --- a/homeassistant/components/homekit/translations/zh-Hans.json +++ b/homeassistant/components/homekit/translations/zh-Hans.json @@ -55,18 +55,9 @@ "description": "\u9664\u975e\u5df2\u9009\u62e9\u4e86\u6307\u5b9a\u7684\u5b9e\u4f53\uff0c\u5426\u5219\u6240\u6709\u201c{domains}\u201d\u7c7b\u578b\u7684\u5b9e\u4f53\u90fd\u5c06\u88ab\u5305\u542b\u3002", "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u5b9e\u4f53" }, - "include_exclude": { - "data": { - "entities": "\u5b9e\u4f53", - "mode": "\u6a21\u5f0f" - }, - "description": "\u9009\u62e9\u8981\u5f00\u653e\u7684\u5b9e\u4f53\u3002\n\u5728\u914d\u4ef6\u6a21\u5f0f\u4e2d\uff0c\u53ea\u80fd\u5f00\u653e\u4e00\u4e2a\u5b9e\u4f53\u3002\u5728\u6865\u63a5\u5305\u542b\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u5305\u542b\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u90fd\u4f1a\u5f00\u653e\u3002\u5728\u6865\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\uff0c\u5982\u679c\u4e0d\u9009\u62e9\u6392\u9664\u7684\u5b9e\u4f53\uff0c\u57df\u4e2d\u6240\u6709\u5b9e\u4f53\u4e5f\u90fd\u4f1a\u5f00\u653e\u3002\n\u4e3a\u83b7\u5f97\u6700\u4f73\u4f53\u9a8c\uff0c\u5c06\u4f1a\u4e3a\u6bcf\u4e2a\u7535\u89c6\u5a92\u4f53\u64ad\u653e\u5668\u3001\u57fa\u4e8e\u6d3b\u52a8\u7684\u9065\u63a7\u5668\u3001\u9501\u548c\u6444\u50cf\u5934\u521b\u5efa\u5355\u72ec\u7684 HomeKit \u914d\u4ef6\u3002", - "title": "\u9009\u62e9\u8981\u5305\u542b\u7684\u5b9e\u4f53" - }, "init": { "data": { "domains": "\u8981\u5305\u542b\u7684\u57df", - "include_domains": "\u8981\u5305\u542b\u7684\u57df", "include_exclude_mode": "\u5305\u542b\u6a21\u5f0f", "mode": "HomeKit \u6a21\u5f0f" }, diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index b5aa54c72c4..3ad28a22f45 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -55,18 +55,9 @@ "description": "\u9664\u975e\u9078\u64c7\u7279\u5b9a\u5be6\u9ad4\u5916\uff0c\u5c07\u6703\u5305\u542b\u6240\u6709 \u201c{domains}\u201d \u5167\u5be6\u9ad4\u3002", "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" }, - "include_exclude": { - "data": { - "entities": "\u5be6\u9ad4", - "mode": "\u6a21\u5f0f" - }, - "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u6bcf\u4e00\u500b\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u3001\u9060\u7aef\u9059\u63a7\u5668\u3001\u9580\u9396\u8207\u651d\u5f71\u6a5f\uff0c\u5c07\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u9032\u884c\u3002", - "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" - }, "init": { "data": { "domains": "\u5305\u542b\u7db2\u57df", - "include_domains": "\u5305\u542b\u7db2\u57df", "include_exclude_mode": "\u5305\u542b\u6a21\u5f0f", "mode": "HomeKit \u6a21\u5f0f" }, diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 51d0480bde3..5879609644c 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -13,9 +13,7 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, + CoverEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -201,11 +199,11 @@ class OpeningDeviceBase(HomeAccessory): state = self.hass.states.get(self.entity_id) self.features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - self._supports_stop = self.features & SUPPORT_STOP + self._supports_stop = self.features & CoverEntityFeature.STOP self.chars = [] if self._supports_stop: self.chars.append(CHAR_HOLD_POSITION) - self._supports_tilt = self.features & SUPPORT_SET_TILT_POSITION + self._supports_tilt = self.features & CoverEntityFeature.SET_TILT_POSITION if self._supports_tilt: self.chars.extend([CHAR_TARGET_TILT_ANGLE, CHAR_CURRENT_TILT_ANGLE]) @@ -276,7 +274,7 @@ class OpeningDevice(OpeningDeviceBase, HomeAccessory): CHAR_CURRENT_POSITION, value=0 ) target_args = {"value": 0} - if self.features & SUPPORT_SET_POSITION: + if self.features & CoverEntityFeature.SET_POSITION: target_args["setter_callback"] = self.move_cover else: # If its tilt only we lock the position state to 0 (closed) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 82d254cb9a5..ecc73f5e731 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -17,9 +17,7 @@ from homeassistant.components.fan import ( SERVICE_SET_DIRECTION, SERVICE_SET_PERCENTAGE, SERVICE_SET_PRESET_MODE, - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, + FanEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -66,11 +64,11 @@ class Fan(HomeAccessory): percentage_step = state.attributes.get(ATTR_PERCENTAGE_STEP, 1) self.preset_modes = state.attributes.get(ATTR_PRESET_MODES) - if features & SUPPORT_DIRECTION: + if features & FanEntityFeature.DIRECTION: self.chars.append(CHAR_ROTATION_DIRECTION) - if features & SUPPORT_OSCILLATE: + if features & FanEntityFeature.OSCILLATE: self.chars.append(CHAR_SWING_MODE) - if features & SUPPORT_SET_SPEED: + if features & FanEntityFeature.SET_SPEED: self.chars.append(CHAR_ROTATION_SPEED) if self.preset_modes and len(self.preset_modes) == 1: self.chars.append(CHAR_TARGET_FAN_STATE) @@ -138,7 +136,7 @@ class Fan(HomeAccessory): # the fan to 100% than to the desired speed. # # Setting the speed will take care of turning - # on the fan if SUPPORT_SET_SPEED is set. + # on the fan if FanEntityFeature.SET_SPEED is set. if not self.char_speed or CHAR_ROTATION_SPEED not in char_values: self.set_state(1) else: diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 081f6f1bdd4..4b433a5dc3a 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -18,10 +18,8 @@ from homeassistant.components.light import ( ATTR_RGBWW_COLOR, ATTR_SUPPORTED_COLOR_MODES, ATTR_WHITE, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_WHITE, DOMAIN, + ColorMode, brightness_supported, color_supported, color_temp_supported, @@ -60,7 +58,7 @@ CHANGE_COALESCE_TIME_WINDOW = 0.01 DEFAULT_MIN_MIREDS = 153 DEFAULT_MAX_MIREDS = 500 -COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW, COLOR_MODE_WHITE} +COLOR_MODES_WITH_WHITES = {ColorMode.RGBW, ColorMode.RGBWW, ColorMode.WHITE} @TYPES.register("Light") @@ -86,9 +84,9 @@ class Light(HomeAccessory): self._previous_color_mode = attributes.get(ATTR_COLOR_MODE) self.color_supported = color_supported(color_modes) self.color_temp_supported = color_temp_supported(color_modes) - self.rgbw_supported = COLOR_MODE_RGBW in color_modes - self.rgbww_supported = COLOR_MODE_RGBWW in color_modes - self.white_supported = COLOR_MODE_WHITE in color_modes + self.rgbw_supported = ColorMode.RGBW in color_modes + self.rgbww_supported = ColorMode.RGBWW in color_modes + self.white_supported = ColorMode.WHITE in color_modes self.brightness_supported = brightness_supported(color_modes) if self.brightness_supported: @@ -264,7 +262,7 @@ class Light(HomeAccessory): hue, saturation = color_temperature_to_hs( color_temperature_mired_to_kelvin(color_temp) ) - elif color_mode == COLOR_MODE_WHITE: + elif color_mode == ColorMode.WHITE: hue, saturation = 0, 0 else: hue, saturation = attributes.get(ATTR_HS_COLOR, (None, None)) @@ -281,7 +279,7 @@ class Light(HomeAccessory): color_temp = None if self.color_temp_supported: color_temp = attributes.get(ATTR_COLOR_TEMP) - elif color_mode == COLOR_MODE_WHITE: + elif color_mode == ColorMode.WHITE: color_temp = self.min_mireds if isinstance(color_temp, (int, float)): self.char_color_temp.set_value(round(color_temp, 0)) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 8faa1385e18..b26016b8adc 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -10,12 +10,7 @@ from homeassistant.components.media_player import ( ATTR_MEDIA_VOLUME_MUTED, DOMAIN, SERVICE_SELECT_SOURCE, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, + MediaPlayerEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -218,7 +213,7 @@ class TelevisionMediaPlayer(RemoteInputSelectAccessory): def __init__(self, *args): """Initialize a Television Media Player accessory object.""" super().__init__( - SUPPORT_SELECT_SOURCE, + MediaPlayerEntityFeature.SELECT_SOURCE, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, *args, @@ -228,12 +223,17 @@ class TelevisionMediaPlayer(RemoteInputSelectAccessory): self.chars_speaker = [] - self._supports_play_pause = features & (SUPPORT_PLAY | SUPPORT_PAUSE) - if features & SUPPORT_VOLUME_MUTE or features & SUPPORT_VOLUME_STEP: + self._supports_play_pause = features & ( + MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE + ) + if ( + features & MediaPlayerEntityFeature.VOLUME_MUTE + or features & MediaPlayerEntityFeature.VOLUME_STEP + ): self.chars_speaker.extend( (CHAR_NAME, CHAR_ACTIVE, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR) ) - if features & SUPPORT_VOLUME_SET: + if features & MediaPlayerEntityFeature.VOLUME_SET: self.chars_speaker.append(CHAR_VOLUME) if CHAR_VOLUME_SELECTOR in self.chars_speaker: diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py index be1ab2a4f94..aa064cfc012 100644 --- a/homeassistant/components/homekit/type_remotes.py +++ b/homeassistant/components/homekit/type_remotes.py @@ -9,7 +9,7 @@ from homeassistant.components.remote import ( ATTR_ACTIVITY_LIST, ATTR_CURRENT_ACTIVITY, DOMAIN as REMOTE_DOMAIN, - SUPPORT_ACTIVITY, + RemoteEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -203,7 +203,7 @@ class ActivityRemote(RemoteInputSelectAccessory): def __init__(self, *args): """Initialize a Activity Remote accessory object.""" super().__init__( - SUPPORT_ACTIVITY, + RemoteEntityFeature.ACTIVITY, ATTR_CURRENT_ACTIVITY, ATTR_ACTIVITY_LIST, *args, diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index d76fbf0f534..f9c881339ce 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -3,13 +3,9 @@ import logging from pyhap.const import CATEGORY_ALARM_SYSTEM -from homeassistant.components.alarm_control_panel import DOMAIN -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_ARM_VACATION, - SUPPORT_ALARM_TRIGGER, +from homeassistant.components.alarm_control_panel import ( + DOMAIN, + AlarmControlPanelEntityFeature, ) from homeassistant.const import ( ATTR_CODE, @@ -91,11 +87,11 @@ class SecuritySystem(HomeAccessory): supported_states = state.attributes.get( ATTR_SUPPORTED_FEATURES, ( - SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_VACATION - | SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_TRIGGER + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_VACATION + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.TRIGGER ), ) @@ -108,15 +104,18 @@ class SecuritySystem(HomeAccessory): current_supported_states = [HK_ALARM_DISARMED, HK_ALARM_TRIGGERED] target_supported_services = [HK_ALARM_DISARMED] - if supported_states & SUPPORT_ALARM_ARM_HOME: + if supported_states & AlarmControlPanelEntityFeature.ARM_HOME: current_supported_states.append(HK_ALARM_STAY_ARMED) target_supported_services.append(HK_ALARM_STAY_ARMED) - if supported_states & (SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_VACATION): + if supported_states & ( + AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_VACATION + ): current_supported_states.append(HK_ALARM_AWAY_ARMED) target_supported_services.append(HK_ALARM_AWAY_ARMED) - if supported_states & SUPPORT_ALARM_ARM_NIGHT: + if supported_states & AlarmControlPanelEntityFeature.ARM_NIGHT: current_supported_states.append(HK_ALARM_NIGHT_ARMED) target_supported_services.append(HK_ALARM_NIGHT_ARMED) diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 594d95494f1..7e9b4cbbb31 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -11,7 +11,6 @@ from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, - DEVICE_CLASS_CO2, STATE_HOME, STATE_ON, TEMP_CELSIUS, @@ -76,7 +75,6 @@ BINARY_SENSOR_SERVICE_MAP: dict[str, SI] = { BinarySensorDeviceClass.CO: SI( SERV_CARBON_MONOXIDE_SENSOR, CHAR_CARBON_MONOXIDE_DETECTED, int ), - DEVICE_CLASS_CO2: SI(SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED, int), BinarySensorDeviceClass.DOOR: SI( SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, int ), diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 7bc529d7e40..1598df015c5 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -20,8 +20,7 @@ from homeassistant.components.vacuum import ( SERVICE_RETURN_TO_BASE, SERVICE_START, STATE_CLEANING, - SUPPORT_RETURN_HOME, - SUPPORT_START, + VacuumEntityFeature, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -188,10 +187,10 @@ class Vacuum(Switch): features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if value: - sup_start = features & SUPPORT_START + sup_start = features & VacuumEntityFeature.START service = SERVICE_START if sup_start else SERVICE_TURN_ON else: - sup_return_home = features & SUPPORT_RETURN_HOME + sup_return_home = features & VacuumEntityFeature.RETURN_HOME service = SERVICE_RETURN_TO_BASE if sup_return_home else SERVICE_TURN_OFF self.async_call_service( diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 1e20d1bc710..fc27a85850f 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -3,6 +3,7 @@ import logging from pyhap.const import CATEGORY_THERMOSTAT +from homeassistant.components.climate import ClimateEntityFeature from homeassistant.components.climate.const import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, @@ -19,12 +20,6 @@ from homeassistant.components.climate.const import ( ATTR_SWING_MODES, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, DEFAULT_MAX_TEMP, DEFAULT_MIN_HUMIDITY, DEFAULT_MIN_TEMP, @@ -36,28 +31,18 @@ from homeassistant.components.climate.const import ( FAN_MIDDLE, FAN_OFF, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE as SERVICE_SET_HVAC_MODE_THERMOSTAT, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE as SERVICE_SET_TEMPERATURE_THERMOSTAT, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_ON, SWING_VERTICAL, + HVACAction, + HVACMode, ) from homeassistant.components.water_heater import ( DOMAIN as DOMAIN_WATER_HEATER, @@ -106,10 +91,10 @@ from .util import temperature_to_homekit, temperature_to_states _LOGGER = logging.getLogger(__name__) DEFAULT_HVAC_MODES = [ - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.HEAT_COOL, + HVACMode.OFF, ] HC_HOMEKIT_VALID_MODES_WATER_HEATER = {"Heat": 1} @@ -144,23 +129,23 @@ HC_MAX_TEMP = 38 UNIT_HOMEKIT_TO_HASS = {c: s for s, c in UNIT_HASS_TO_HOMEKIT.items()} HC_HASS_TO_HOMEKIT = { - HVAC_MODE_OFF: HC_HEAT_COOL_OFF, - HVAC_MODE_HEAT: HC_HEAT_COOL_HEAT, - HVAC_MODE_COOL: HC_HEAT_COOL_COOL, - HVAC_MODE_AUTO: HC_HEAT_COOL_AUTO, - HVAC_MODE_HEAT_COOL: HC_HEAT_COOL_AUTO, - HVAC_MODE_DRY: HC_HEAT_COOL_COOL, - HVAC_MODE_FAN_ONLY: HC_HEAT_COOL_COOL, + HVACMode.OFF: HC_HEAT_COOL_OFF, + HVACMode.HEAT: HC_HEAT_COOL_HEAT, + HVACMode.COOL: HC_HEAT_COOL_COOL, + HVACMode.AUTO: HC_HEAT_COOL_AUTO, + HVACMode.HEAT_COOL: HC_HEAT_COOL_AUTO, + HVACMode.DRY: HC_HEAT_COOL_COOL, + HVACMode.FAN_ONLY: HC_HEAT_COOL_COOL, } HC_HOMEKIT_TO_HASS = {c: s for s, c in HC_HASS_TO_HOMEKIT.items()} HC_HASS_TO_HOMEKIT_ACTION = { - CURRENT_HVAC_OFF: HC_HEAT_COOL_OFF, - CURRENT_HVAC_IDLE: HC_HEAT_COOL_OFF, - CURRENT_HVAC_HEAT: HC_HEAT_COOL_HEAT, - CURRENT_HVAC_COOL: HC_HEAT_COOL_COOL, - CURRENT_HVAC_DRY: HC_HEAT_COOL_COOL, - CURRENT_HVAC_FAN: HC_HEAT_COOL_COOL, + HVACAction.OFF: HC_HEAT_COOL_OFF, + HVACAction.IDLE: HC_HEAT_COOL_OFF, + HVACAction.HEATING: HC_HEAT_COOL_HEAT, + HVACAction.COOLING: HC_HEAT_COOL_COOL, + HVACAction.DRYING: HC_HEAT_COOL_COOL, + HVACAction.FAN: HC_HEAT_COOL_COOL, } FAN_STATE_INACTIVE = 0 @@ -168,12 +153,12 @@ FAN_STATE_IDLE = 1 FAN_STATE_ACTIVE = 2 HC_HASS_TO_HOMEKIT_FAN_STATE = { - CURRENT_HVAC_OFF: FAN_STATE_INACTIVE, - CURRENT_HVAC_IDLE: FAN_STATE_IDLE, - CURRENT_HVAC_HEAT: FAN_STATE_ACTIVE, - CURRENT_HVAC_COOL: FAN_STATE_ACTIVE, - CURRENT_HVAC_DRY: FAN_STATE_ACTIVE, - CURRENT_HVAC_FAN: FAN_STATE_ACTIVE, + HVACAction.OFF: FAN_STATE_INACTIVE, + HVACAction.IDLE: FAN_STATE_IDLE, + HVACAction.HEATING: FAN_STATE_ACTIVE, + HVACAction.COOLING: FAN_STATE_ACTIVE, + HVACAction.DRYING: FAN_STATE_ACTIVE, + HVACAction.FAN: FAN_STATE_ACTIVE, } HEAT_COOL_DEADBAND = 5 @@ -199,12 +184,12 @@ class Thermostat(HomeAccessory): min_humidity = attributes.get(ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY) features = attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if features & SUPPORT_TARGET_TEMPERATURE_RANGE: + if features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE: self.chars.extend( (CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE) ) - if features & SUPPORT_TARGET_HUMIDITY: + if features & ClimateEntityFeature.TARGET_HUMIDITY: self.chars.extend((CHAR_TARGET_HUMIDITY, CHAR_CURRENT_HUMIDITY)) serv_thermostat = self.add_preload_service(SERV_THERMOSTAT, self.chars) @@ -288,7 +273,7 @@ class Thermostat(HomeAccessory): fan_modes = {} self.ordered_fan_speeds = [] - if features & SUPPORT_FAN_MODE: + if features & ClimateEntityFeature.FAN_MODE: fan_modes = { fan_mode.lower(): fan_mode for fan_mode in attributes.get(ATTR_FAN_MODES) or [] @@ -304,7 +289,7 @@ class Thermostat(HomeAccessory): self.fan_modes = fan_modes if ( - features & SUPPORT_SWING_MODE + features & ClimateEntityFeature.SWING_MODE and (swing_modes := attributes.get(ATTR_SWING_MODES)) and PRE_DEFINED_SWING_MODES.intersection(swing_modes) ): @@ -459,14 +444,14 @@ class Thermostat(HomeAccessory): if CHAR_TARGET_TEMPERATURE in char_values: hc_target_temp = char_values[CHAR_TARGET_TEMPERATURE] - if features & SUPPORT_TARGET_TEMPERATURE: + if features & ClimateEntityFeature.TARGET_TEMPERATURE: service = SERVICE_SET_TEMPERATURE_THERMOSTAT temperature = self._temperature_to_states(hc_target_temp) events.append( f"{CHAR_TARGET_TEMPERATURE} to {char_values[CHAR_TARGET_TEMPERATURE]}°C" ) params[ATTR_TEMPERATURE] = temperature - elif features & SUPPORT_TARGET_TEMPERATURE_RANGE: + elif features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE: # Homekit will send us a target temperature # even if the device does not support it _LOGGER.debug( @@ -543,17 +528,17 @@ class Thermostat(HomeAccessory): # heating or cooling comes on to maintain a target temp which is closest to # the Home Assistant spec # - # HVAC_MODE_HEAT_COOL: The device supports heating/cooling to a range + # HVACMode.HEAT_COOL: The device supports heating/cooling to a range self.hc_homekit_to_hass = { c: s for s, c in HC_HASS_TO_HOMEKIT.items() if ( s in hc_modes and not ( - (s == HVAC_MODE_AUTO and HVAC_MODE_HEAT_COOL in hc_modes) + (s == HVACMode.AUTO and HVACMode.HEAT_COOL in hc_modes) or ( - s in (HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY) - and HVAC_MODE_COOL in hc_modes + s in (HVACMode.DRY, HVACMode.FAN_ONLY) + and HVACMode.COOL in hc_modes ) ) ) @@ -657,7 +642,10 @@ class Thermostat(HomeAccessory): # Update target temperature target_temp = _get_target_temperature(new_state, self._unit) - if target_temp is None and features & SUPPORT_TARGET_TEMPERATURE_RANGE: + if ( + target_temp is None + and features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ): # Homekit expects a target temperature # even if the device does not support it hc_hvac_mode = self.char_target_heat_cool.value @@ -712,7 +700,7 @@ class Thermostat(HomeAccessory): ) self.char_active.set_value( - int(new_state.state != HVAC_MODE_OFF and fan_mode_lower != FAN_OFF) + int(new_state.state != HVACMode.OFF and fan_mode_lower != FAN_OFF) ) @@ -770,7 +758,7 @@ class WaterHeater(HomeAccessory): def set_heat_cool(self, value): """Change operation mode to value if call came from HomeKit.""" _LOGGER.debug("%s: Set heat-cool to %d", self.entity_id, value) - if HC_HOMEKIT_TO_HASS[value] != HVAC_MODE_HEAT: + if HC_HOMEKIT_TO_HASS[value] != HVACMode.HEAT: self.char_target_heat_cool.set_value(1) # Heat def set_target_temperature(self, value): diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index be5c166b71d..8b010f85fb6 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -25,8 +25,9 @@ from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.media_player import ( DOMAIN as MEDIA_PLAYER_DOMAIN, MediaPlayerDeviceClass, + MediaPlayerEntityFeature, ) -from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN, SUPPORT_ACTIVITY +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN, RemoteEntityFeature from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, @@ -296,14 +297,14 @@ def get_media_player_features(state: State) -> list[str]: supported_modes = [] if features & ( - media_player.const.SUPPORT_TURN_ON | media_player.const.SUPPORT_TURN_OFF + MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF ): supported_modes.append(FEATURE_ON_OFF) - if features & (media_player.const.SUPPORT_PLAY | media_player.const.SUPPORT_PAUSE): + if features & (MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE): supported_modes.append(FEATURE_PLAY_PAUSE) - if features & (media_player.const.SUPPORT_PLAY | media_player.const.SUPPORT_STOP): + if features & (MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.STOP): supported_modes.append(FEATURE_PLAY_STOP) - if features & media_player.const.SUPPORT_VOLUME_MUTE: + if features & MediaPlayerEntityFeature.VOLUME_MUTE: supported_modes.append(FEATURE_TOGGLE_MUTE) return supported_modes @@ -568,5 +569,6 @@ def state_needs_accessory_mode(state: State) -> bool: state.domain == MEDIA_PLAYER_DOMAIN and state.attributes.get(ATTR_DEVICE_CLASS) == MediaPlayerDeviceClass.TV or state.domain == REMOTE_DOMAIN - and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & SUPPORT_ACTIVITY + and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & RemoteEntityFeature.ACTIVITY ) diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 0194036db4e..c7e499b6e89 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -6,11 +6,9 @@ from typing import Any from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import Service, ServicesTypes -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -67,6 +65,12 @@ async def async_setup_entry( class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): """Representation of a Homekit Alarm Control Panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + ) + def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" return [ @@ -87,11 +91,6 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): self.service.value(CharacteristicsTypes.SECURITY_SYSTEM_STATE_CURRENT) ] - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" await self.set_alarm_state(STATE_ALARM_DISARMED, code) diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 53aee8561e4..44ecee13875 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -25,20 +25,11 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, SWING_OFF, SWING_VERTICAL, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -51,16 +42,16 @@ _LOGGER = logging.getLogger(__name__) # Map of Homekit operation modes to hass modes MODE_HOMEKIT_TO_HASS = { - HeatingCoolingTargetValues.OFF: HVAC_MODE_OFF, - HeatingCoolingTargetValues.HEAT: HVAC_MODE_HEAT, - HeatingCoolingTargetValues.COOL: HVAC_MODE_COOL, - HeatingCoolingTargetValues.AUTO: HVAC_MODE_HEAT_COOL, + HeatingCoolingTargetValues.OFF: HVACMode.OFF, + HeatingCoolingTargetValues.HEAT: HVACMode.HEAT, + HeatingCoolingTargetValues.COOL: HVACMode.COOL, + HeatingCoolingTargetValues.AUTO: HVACMode.HEAT_COOL, } CURRENT_MODE_HOMEKIT_TO_HASS = { - HeatingCoolingCurrentValues.IDLE: CURRENT_HVAC_IDLE, - HeatingCoolingCurrentValues.HEATING: CURRENT_HVAC_HEAT, - HeatingCoolingCurrentValues.COOLING: CURRENT_HVAC_COOL, + HeatingCoolingCurrentValues.IDLE: HVACAction.IDLE, + HeatingCoolingCurrentValues.HEATING: HVACAction.HEATING, + HeatingCoolingCurrentValues.COOLING: HVACAction.COOLING, } SWING_MODE_HOMEKIT_TO_HASS = { @@ -69,16 +60,16 @@ SWING_MODE_HOMEKIT_TO_HASS = { } CURRENT_HEATER_COOLER_STATE_HOMEKIT_TO_HASS = { - CurrentHeaterCoolerStateValues.INACTIVE: CURRENT_HVAC_OFF, - CurrentHeaterCoolerStateValues.IDLE: CURRENT_HVAC_IDLE, - CurrentHeaterCoolerStateValues.HEATING: CURRENT_HVAC_HEAT, - CurrentHeaterCoolerStateValues.COOLING: CURRENT_HVAC_COOL, + CurrentHeaterCoolerStateValues.INACTIVE: HVACAction.OFF, + CurrentHeaterCoolerStateValues.IDLE: HVACAction.IDLE, + CurrentHeaterCoolerStateValues.HEATING: HVACAction.HEATING, + CurrentHeaterCoolerStateValues.COOLING: HVACAction.COOLING, } TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS = { - TargetHeaterCoolerStateValues.AUTOMATIC: HVAC_MODE_HEAT_COOL, - TargetHeaterCoolerStateValues.HEAT: HVAC_MODE_HEAT, - TargetHeaterCoolerStateValues.COOL: HVAC_MODE_COOL, + TargetHeaterCoolerStateValues.AUTOMATIC: HVACMode.HEAT_COOL, + TargetHeaterCoolerStateValues.HEAT: HVACMode.HEAT, + TargetHeaterCoolerStateValues.COOL: HVACMode.COOL, } # Map of hass operation modes to homekit modes @@ -149,14 +140,14 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): hvac_mode, ) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_put_characteristics( {CharacteristicsTypes.ACTIVE: ActivationStateValues.INACTIVE} ) return - if hvac_mode not in {HVAC_MODE_HEAT, HVAC_MODE_COOL}: + if hvac_mode not in {HVACMode.HEAT, HVACMode.COOL}: _LOGGER.warning( "HomeKit device %s: Setting temperature in %s mode is not supported yet;" " Consider raising a ticket if you have this device and want to help us implement this feature", @@ -259,7 +250,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return super().max_temp @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation.""" # This characteristic describes the current mode of a device, # e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. @@ -268,12 +259,12 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): self.service.value(CharacteristicsTypes.ACTIVE) == ActivationStateValues.INACTIVE ): - return CURRENT_HVAC_OFF + return HVACAction.OFF value = self.service.value(CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE) return CURRENT_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(value) @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" # This characteristic describes the target mode # E.g. should the device start heating a room if the temperature @@ -283,12 +274,12 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): self.service.value(CharacteristicsTypes.ACTIVE) == ActivationStateValues.INACTIVE ): - return HVAC_MODE_OFF + return HVACMode.OFF value = self.service.value(CharacteristicsTypes.TARGET_HEATER_COOLER_STATE) return TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS[value] @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" valid_values = clamp_enum_to_char( TargetHeaterCoolerStateValues, @@ -297,14 +288,14 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): modes = [ TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS[mode] for mode in valid_values ] - modes.append(HVAC_MODE_OFF) + modes.append(HVACMode.OFF) return modes @property def swing_mode(self) -> str: """Return the swing setting. - Requires SUPPORT_SWING_MODE. + Requires ClimateEntityFeature.SWING_MODE. """ value = self.service.value(CharacteristicsTypes.SWING_MODE) return SWING_MODE_HOMEKIT_TO_HASS[value] @@ -313,7 +304,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): def swing_modes(self) -> list[str]: """Return the list of available swing modes. - Requires SUPPORT_SWING_MODE. + Requires ClimateEntityFeature.SWING_MODE. """ valid_values = clamp_enum_to_char( SwingModeValues, @@ -333,13 +324,13 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): features = 0 if self.service.has(CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD): - features |= SUPPORT_TARGET_TEMPERATURE + features |= ClimateEntityFeature.TARGET_TEMPERATURE if self.service.has(CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD): - features |= SUPPORT_TARGET_TEMPERATURE + features |= ClimateEntityFeature.TARGET_TEMPERATURE if self.service.has(CharacteristicsTypes.SWING_MODE): - features |= SUPPORT_SWING_MODE + features |= ClimateEntityFeature.SWING_MODE return features @@ -383,8 +374,10 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): cool_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) if ( - (mode == HVAC_MODE_HEAT_COOL) - and (SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features) + (mode == HVACMode.HEAT_COOL) + and ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE & self.supported_features + ) and heat_temp and cool_temp ): @@ -408,7 +401,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): {CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET: humidity} ) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" await self.async_put_characteristics( { @@ -427,9 +420,11 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT, HVAC_MODE_COOL}) or ( - (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) - and not (SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features) + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVACMode.HEAT, HVACMode.COOL}) or ( + (MODE_HOMEKIT_TO_HASS.get(value) in {HVACMode.HEAT_COOL}) + and not ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE & self.supported_features + ) ): return self.service.value(CharacteristicsTypes.TEMPERATURE_TARGET) return None @@ -438,8 +433,8 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( - SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVACMode.HEAT_COOL}) and ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE & self.supported_features ): return self.service.value( CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD @@ -450,8 +445,8 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( - SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVACMode.HEAT_COOL}) and ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE & self.supported_features ): return self.service.value( CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD @@ -462,8 +457,8 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def min_temp(self) -> float: """Return the minimum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( - SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVACMode.HEAT_COOL}) and ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE & self.supported_features ): min_temp = self.service[ CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD @@ -471,9 +466,9 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): if min_temp is not None: return min_temp elif MODE_HOMEKIT_TO_HASS.get(value) in { - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.HEAT_COOL, }: min_temp = self.service[CharacteristicsTypes.TEMPERATURE_TARGET].minValue if min_temp is not None: @@ -484,8 +479,8 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): def max_temp(self) -> float: """Return the maximum target temp.""" value = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET) - if (MODE_HOMEKIT_TO_HASS.get(value) in {HVAC_MODE_HEAT_COOL}) and ( - SUPPORT_TARGET_TEMPERATURE_RANGE & self.supported_features + if (MODE_HOMEKIT_TO_HASS.get(value) in {HVACMode.HEAT_COOL}) and ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE & self.supported_features ): max_temp = self.service[ CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD @@ -493,9 +488,9 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): if max_temp is not None: return max_temp elif MODE_HOMEKIT_TO_HASS.get(value) in { - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.HEAT_COOL, }: max_temp = self.service[CharacteristicsTypes.TEMPERATURE_TARGET].maxValue if max_temp is not None: @@ -533,7 +528,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return super().max_humidity @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation.""" # This characteristic describes the current mode of a device, # e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit. @@ -542,7 +537,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return CURRENT_MODE_HOMEKIT_TO_HASS.get(value) @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" # This characteristic describes the target mode # E.g. should the device start heating a room if the temperature @@ -552,7 +547,7 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): return MODE_HOMEKIT_TO_HASS[value] @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" valid_values = clamp_enum_to_char( HeatingCoolingTargetValues, @@ -566,15 +561,15 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): features = 0 if self.service.has(CharacteristicsTypes.TEMPERATURE_TARGET): - features |= SUPPORT_TARGET_TEMPERATURE + features |= ClimateEntityFeature.TARGET_TEMPERATURE if self.service.has( CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD ) and self.service.has(CharacteristicsTypes.TEMPERATURE_HEATING_THRESHOLD): - features |= SUPPORT_TARGET_TEMPERATURE_RANGE + features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE if self.service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET): - features |= SUPPORT_TARGET_HUMIDITY + features |= ClimateEntityFeature.TARGET_HUMIDITY return features diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index a1aa8dbd807..4e8af03bba0 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -9,15 +9,9 @@ from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - DEVICE_CLASS_GARAGE, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, + CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING @@ -64,7 +58,8 @@ async def async_setup_entry( class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Garage Door.""" - _attr_device_class = DEVICE_CLASS_GARAGE + _attr_device_class = CoverDeviceClass.GARAGE + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" @@ -74,11 +69,6 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): CharacteristicsTypes.OBSTRUCTION_DETECTED, ] - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE - @property def _state(self) -> str: """Return the current state of the garage door.""" @@ -143,10 +133,14 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): @property def supported_features(self) -> int: """Flag supported features.""" - features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) if self.service.has(CharacteristicsTypes.POSITION_HOLD): - features |= SUPPORT_STOP + features |= CoverEntityFeature.STOP supports_tilt = any( ( @@ -157,7 +151,9 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): if supports_tilt: features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION ) return features diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index 3f48d34559b..80c2f9870c1 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -9,10 +9,8 @@ from aiohomekit.model.services import Service, ServicesTypes from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -76,13 +74,13 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): features = 0 if self.service.has(CharacteristicsTypes.ROTATION_DIRECTION): - features |= SUPPORT_DIRECTION + features |= FanEntityFeature.DIRECTION if self.service.has(CharacteristicsTypes.ROTATION_SPEED): - features |= SUPPORT_SET_SPEED + features |= FanEntityFeature.SET_SPEED if self.service.has(CharacteristicsTypes.SWING_MODE): - features |= SUPPORT_OSCILLATE + features |= FanEntityFeature.OSCILLATE return features @@ -127,7 +125,10 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): if not self.is_on: characteristics[self.on_characteristic] = True - if percentage is not None and self.supported_features & SUPPORT_SET_SPEED: + if ( + percentage is not None + and self.supported_features & FanEntityFeature.SET_SPEED + ): characteristics[CharacteristicsTypes.ROTATION_SPEED] = percentage if characteristics: diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index fcca3e54725..1676999ad78 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -6,13 +6,16 @@ from typing import Any from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import Service, ServicesTypes -from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) from homeassistant.components.humidifier.const import ( DEFAULT_MAX_HUMIDITY, DEFAULT_MIN_HUMIDITY, MODE_AUTO, MODE_NORMAL, - SUPPORT_MODES, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -20,8 +23,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import KNOWN_DEVICES, HomeKitEntity -SUPPORT_FLAGS = 0 - HK_MODE_TO_HA = { 0: "off", 1: MODE_AUTO, @@ -40,6 +41,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): """Representation of a HomeKit Controller Humidifier.""" _attr_device_class = HumidifierDeviceClass.HUMIDIFIER + _attr_supported_features = HumidifierEntityFeature.MODES def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" @@ -50,11 +52,6 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD, ] - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_FLAGS | SUPPORT_MODES - @property def is_on(self) -> bool: """Return true if device is on.""" @@ -79,7 +76,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. - Requires SUPPORT_MODES. + Requires HumidifierEntityFeature.MODES. """ mode = self.service.value( CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE @@ -90,7 +87,7 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): def available_modes(self) -> list[str] | None: """Return a list of available modes. - Requires SUPPORT_MODES. + Requires HumidifierEntityFeature.MODES. """ available_modes = [ MODE_NORMAL, @@ -147,6 +144,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): """Representation of a HomeKit Controller Humidifier.""" _attr_device_class = HumidifierDeviceClass.DEHUMIDIFIER + _attr_supported_features = HumidifierEntityFeature.MODES def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" @@ -158,11 +156,6 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD, ] - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_FLAGS | SUPPORT_MODES - @property def is_on(self) -> bool: """Return true if device is on.""" @@ -187,7 +180,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. - Requires SUPPORT_MODES. + Requires HumidifierEntityFeature.MODES. """ mode = self.service.value( CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE @@ -198,7 +191,7 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): def available_modes(self) -> list[str] | None: """Return a list of available modes. - Requires SUPPORT_MODES. + Requires HumidifierEntityFeature.MODES. """ available_modes = [ MODE_NORMAL, diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 2b82f27022b..4073c1ac9fe 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -10,9 +10,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -79,23 +77,43 @@ class HomeKitLight(HomeKitEntity, LightEntity): return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE) @property - def supported_features(self) -> int: - """Flag supported features.""" - features = 0 - - if self.service.has(CharacteristicsTypes.BRIGHTNESS): - features |= SUPPORT_BRIGHTNESS + def color_mode(self) -> str: + """Return the color mode of the light.""" + # aiohomekit does not keep track of the light's color mode, report + # hs for light supporting both hs and ct + if self.service.has(CharacteristicsTypes.HUE) or self.service.has( + CharacteristicsTypes.SATURATION + ): + return ColorMode.HS if self.service.has(CharacteristicsTypes.COLOR_TEMPERATURE): - features |= SUPPORT_COLOR_TEMP + return ColorMode.COLOR_TEMP - if self.service.has(CharacteristicsTypes.HUE): - features |= SUPPORT_COLOR + if self.service.has(CharacteristicsTypes.BRIGHTNESS): + return ColorMode.BRIGHTNESS - if self.service.has(CharacteristicsTypes.SATURATION): - features |= SUPPORT_COLOR + return ColorMode.ONOFF - return features + @property + def supported_color_modes(self) -> set[ColorMode | str] | None: + """Flag supported color modes.""" + color_modes: set[ColorMode | str] = set() + + if self.service.has(CharacteristicsTypes.HUE) or self.service.has( + CharacteristicsTypes.SATURATION + ): + color_modes.add(ColorMode.HS) + + if self.service.has(CharacteristicsTypes.COLOR_TEMPERATURE): + color_modes.add(ColorMode.COLOR_TEMP) + + if not color_modes and self.service.has(CharacteristicsTypes.BRIGHTNESS): + color_modes.add(ColorMode.BRIGHTNESS) + + if not color_modes: + color_modes.add(ColorMode.ONOFF) + + return color_modes async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified light on.""" diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 6314efe9dc4..fbdf800edf8 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -15,12 +15,7 @@ from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, -) -from homeassistant.components.media_player.const import ( - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -89,23 +84,23 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): features = 0 if self.service.has(CharacteristicsTypes.ACTIVE_IDENTIFIER): - features |= SUPPORT_SELECT_SOURCE + features |= MediaPlayerEntityFeature.SELECT_SOURCE if self.service.has(CharacteristicsTypes.TARGET_MEDIA_STATE): if TargetMediaStateValues.PAUSE in self.supported_media_states: - features |= SUPPORT_PAUSE + features |= MediaPlayerEntityFeature.PAUSE if TargetMediaStateValues.PLAY in self.supported_media_states: - features |= SUPPORT_PLAY + features |= MediaPlayerEntityFeature.PLAY if TargetMediaStateValues.STOP in self.supported_media_states: - features |= SUPPORT_STOP + features |= MediaPlayerEntityFeature.STOP if ( self.service.has(CharacteristicsTypes.REMOTE_KEY) and RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys ): - features |= SUPPORT_PAUSE | SUPPORT_PLAY + features |= MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY return features diff --git a/homeassistant/components/homekit_controller/translations/hu.json b/homeassistant/components/homekit_controller/translations/hu.json index 6fad9050a20..607c46ede6d 100644 --- a/homeassistant/components/homekit_controller/translations/hu.json +++ b/homeassistant/components/homekit_controller/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Nem adhat\u00f3 hozz\u00e1 p\u00e1ros\u00edt\u00e1s, mert az eszk\u00f6z m\u00e1r nem tal\u00e1lhat\u00f3.", "already_configured": "A tartoz\u00e9k m\u00e1r konfigur\u00e1lva van ezzel a vez\u00e9rl\u0151vel.", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "already_paired": "Ez a tartoz\u00e9k m\u00e1r p\u00e1ros\u00edtva van egy m\u00e1sik eszk\u00f6zzel. \u00c1ll\u00edtsa alaphelyzetbe a tartoz\u00e9kot, majd pr\u00f3b\u00e1lkozzon \u00fajra.", "ignored_model": "A HomeKit t\u00e1mogat\u00e1sa e modelln\u00e9l blokkolva van, mivel a szolg\u00e1ltat\u00e1shoz teljes nat\u00edv integr\u00e1ci\u00f3 \u00e9rhet\u0151 el.", "invalid_config_entry": "Ez az eszk\u00f6z k\u00e9szen \u00e1ll a p\u00e1ros\u00edt\u00e1sra, de m\u00e1r van egy \u00fctk\u00f6z\u0151 konfigur\u00e1ci\u00f3s bejegyz\u00e9s Home Assistantban, amelyet el\u0151sz\u00f6r el kell t\u00e1vol\u00edtani.", diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index 55a51f413bb..67a36284c58 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -3,15 +3,12 @@ from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -34,8 +31,6 @@ HM_PRESET_MAP = { HM_CONTROL_MODE = "CONTROL_MODE" HMIP_CONTROL_MODE = "SET_POINT_MODE" -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - def setup_platform( hass: HomeAssistant, @@ -58,10 +53,9 @@ def setup_platform( class HMThermostat(HMDevice, ClimateEntity): """Representation of a Homematic thermostat.""" - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) @property def temperature_unit(self): @@ -69,32 +63,32 @@ class HMThermostat(HMDevice, ClimateEntity): return TEMP_CELSIUS @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ if self.target_temperature <= self._hmdevice.OFF_VALUE + 0.5: - return HVAC_MODE_OFF + return HVACMode.OFF if "MANU_MODE" in self._hmdevice.ACTIONNODE: if self._hm_control_mode == self._hmdevice.MANU_MODE: - return HVAC_MODE_HEAT - return HVAC_MODE_AUTO + return HVACMode.HEAT + return HVACMode.AUTO # Simple devices if self._data.get("BOOST_MODE"): - return HVAC_MODE_AUTO - return HVAC_MODE_HEAT + return HVACMode.AUTO + return HVACMode.HEAT @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. """ if "AUTO_MODE" in self._hmdevice.ACTIONNODE: - return [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] - return [HVAC_MODE_HEAT, HVAC_MODE_OFF] + return [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF] + return [HVACMode.HEAT, HVACMode.OFF] @property def preset_mode(self): @@ -109,7 +103,7 @@ class HMThermostat(HMDevice, ClimateEntity): mode = mode.lower() # Filter HVAC states - if mode not in (HVAC_MODE_AUTO, HVAC_MODE_HEAT): + if mode not in (HVACMode.AUTO, HVACMode.HEAT): return PRESET_NONE return mode @@ -148,13 +142,13 @@ class HMThermostat(HMDevice, ClimateEntity): self._hmdevice.writeNodeData(self._state, float(temperature)) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_AUTO: + if hvac_mode == HVACMode.AUTO: self._hmdevice.MODE = self._hmdevice.AUTO_MODE - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: self._hmdevice.MODE = self._hmdevice.MANU_MODE - elif hvac_mode == HVAC_MODE_OFF: + elif hvac_mode == HVACMode.OFF: self._hmdevice.turnoff() def set_preset_mode(self, preset_mode: str) -> None: diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index 711d939900c..4087f83dd2a 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -7,12 +7,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -21,8 +18,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ATTR_DISCOVER_DEVICES from .entity import HMDevice -SUPPORT_HOMEMATIC = SUPPORT_BRIGHTNESS - def setup_platform( hass: HomeAssistant, @@ -62,22 +57,40 @@ class HMLight(HMDevice, LightEntity): return False @property - def supported_features(self): - """Flag supported features.""" - features = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if "COLOR" in self._hmdevice.WRITENODE: + return ColorMode.HS + if hasattr(self._hmdevice, "get_color_temp"): + return ColorMode.COLOR_TEMP + return ColorMode.BRIGHTNESS + + @property + def supported_color_modes(self) -> set[ColorMode | str]: + """Flag supported color modes.""" + color_modes: set[ColorMode | str] = set() if "COLOR" in self._hmdevice.WRITENODE: - features |= SUPPORT_COLOR - if "PROGRAM" in self._hmdevice.WRITENODE: - features |= SUPPORT_EFFECT + color_modes.add(ColorMode.HS) if hasattr(self._hmdevice, "get_color_temp"): - features |= SUPPORT_COLOR_TEMP + color_modes.add(ColorMode.COLOR_TEMP) + if not color_modes: + color_modes.add(ColorMode.BRIGHTNESS) + + return color_modes + + @property + def supported_features(self): + """Flag supported features.""" + features = LightEntityFeature.TRANSITION + if "PROGRAM" in self._hmdevice.WRITENODE: + features |= LightEntityFeature.EFFECT return features @property def hs_color(self): """Return the hue and saturation color value [float, float].""" - if not self.supported_features & SUPPORT_COLOR: + if ColorMode.HS not in self.supported_color_modes: return None hue, sat = self._hmdevice.get_hs_color(self._channel) return hue * 360.0, sat * 100.0 @@ -85,7 +98,7 @@ class HMLight(HMDevice, LightEntity): @property def color_temp(self): """Return the color temp in mireds [int].""" - if not self.supported_features & SUPPORT_COLOR_TEMP: + if ColorMode.COLOR_TEMP not in self.supported_color_modes: return None hm_color_temp = self._hmdevice.get_color_temp(self._channel) return self.max_mireds - (self.max_mireds - self.min_mireds) * hm_color_temp @@ -93,14 +106,14 @@ class HMLight(HMDevice, LightEntity): @property def effect_list(self): """Return the list of supported effects.""" - if not self.supported_features & SUPPORT_EFFECT: + if not self.supported_features & LightEntityFeature.EFFECT: return None return self._hmdevice.get_effect_list() @property def effect(self): """Return the current color change program of the light.""" - if not self.supported_features & SUPPORT_EFFECT: + if not self.supported_features & LightEntityFeature.EFFECT: return None return self._hmdevice.get_effect() @@ -119,7 +132,7 @@ class HMLight(HMDevice, LightEntity): ): self._hmdevice.on(self._channel) - if ATTR_HS_COLOR in kwargs and self.supported_features & SUPPORT_COLOR: + if ATTR_HS_COLOR in kwargs: self._hmdevice.set_hs_color( hue=kwargs[ATTR_HS_COLOR][0] / 360.0, saturation=kwargs[ATTR_HS_COLOR][1] / 100.0, @@ -146,7 +159,7 @@ class HMLight(HMDevice, LightEntity): self._state = "LEVEL" self._data[self._state] = None - if self.supported_features & SUPPORT_COLOR: + if ColorMode.HS in self.supported_color_modes: self._data.update({"COLOR": None}) - if self.supported_features & SUPPORT_EFFECT: + if self.supported_features & LightEntityFeature.EFFECT: self._data.update({"PROGRAM": None}) diff --git a/homeassistant/components/homematic/lock.py b/homeassistant/components/homematic/lock.py index 1ddeb474058..32ee4698736 100644 --- a/homeassistant/components/homematic/lock.py +++ b/homeassistant/components/homematic/lock.py @@ -1,7 +1,7 @@ """Support for Homematic locks.""" from __future__ import annotations -from homeassistant.components.lock import SUPPORT_OPEN, LockEntity +from homeassistant.components.lock import LockEntity, LockEntityFeature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -30,6 +30,8 @@ def setup_platform( class HMLock(HMDevice, LockEntity): """Representation of a Homematic lock aka KeyMatic.""" + _attr_supported_features = LockEntityFeature.OPEN + @property def is_locked(self): """Return true if the lock is locked.""" @@ -51,8 +53,3 @@ class HMLock(HMDevice, LockEntity): """Generate the data dictionary (self._data) from metadata.""" self._state = "STATE" self._data.update({self._state: None}) - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_OPEN diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index a96d29f5794..86e187410b3 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -5,10 +5,9 @@ import logging from homematicip.functionalHomes import SecurityAndAlarmHome -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -42,6 +41,11 @@ async def async_setup_entry( class HomematicipAlarmControlPanelEntity(AlarmControlPanelEntity): """Representation of the HomematicIP alarm control panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + def __init__(self, hap: HomematicipHAP) -> None: """Initialize the alarm control panel.""" self._home: AsyncHome = hap.home @@ -79,11 +83,6 @@ class HomematicipAlarmControlPanelEntity(AlarmControlPanelEntity): def _security_and_alarm(self) -> SecurityAndAlarmHome: return self._home.get_functionalHome(SecurityAndAlarmHome) - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - async def async_alarm_disarm(self, code=None) -> None: """Send disarm command.""" await self._home.set_security_zones_activation(False, False) diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index dffba646dfd..23da237331b 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -11,18 +11,13 @@ from homematicip.functionalHomes import IndoorClimateHome from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_ECO, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -68,6 +63,10 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): Cool mode is only available for floor heating systems, if basically enabled in the hmip app. """ + _attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) + def __init__(self, hap: HomematicipHAP, device: AsyncHeatingGroup) -> None: """Initialize heating group.""" device.modelType = "HmIP-Heating-Group" @@ -92,11 +91,6 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): """Return the unit of measurement.""" return TEMP_CELSIUS - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE - @property def target_temperature(self) -> float: """Return the temperature we try to reach.""" @@ -115,31 +109,29 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): return self._device.humidity @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie.""" if self._disabled_by_cooling_mode and not self._has_switch: - return HVAC_MODE_OFF + return HVACMode.OFF if self._device.boostMode: - return HVAC_MODE_HEAT + return HVACMode.HEAT if self._device.controlMode == HMIP_MANUAL_CM: - return HVAC_MODE_HEAT if self._heat_mode_enabled else HVAC_MODE_COOL + return HVACMode.HEAT if self._heat_mode_enabled else HVACMode.COOL - return HVAC_MODE_AUTO + return HVACMode.AUTO @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" if self._disabled_by_cooling_mode and not self._has_switch: - return [HVAC_MODE_OFF] + return [HVACMode.OFF] - return ( - [HVAC_MODE_AUTO, HVAC_MODE_HEAT] - if self._heat_mode_enabled - else [HVAC_MODE_AUTO, HVAC_MODE_COOL] - ) + if self._heat_mode_enabled: + return [HVACMode.AUTO, HVACMode.HEAT] + return [HVACMode.AUTO, HVACMode.COOL] @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """ Return the current hvac_action. @@ -150,9 +142,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): and self._has_radiator_thermostat and self._heat_mode_enabled ): - return ( - CURRENT_HVAC_HEAT if self._device.valvePosition else CURRENT_HVAC_IDLE - ) + return HVACAction.HEATING if self._device.valvePosition else HVACAction.IDLE return None @@ -161,7 +151,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): """Return the current preset mode.""" if self._device.boostMode: return PRESET_BOOST - if self.hvac_mode in (HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF): + if self.hvac_mode in (HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF): return PRESET_NONE if self._device.controlMode == HMIP_ECO_CM: if self._indoor_climate.absenceType == AbsenceType.VACATION: @@ -216,12 +206,12 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): if self.min_temp <= temperature <= self.max_temp: await self._device.set_point_temperature(temperature) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" if hvac_mode not in self.hvac_modes: return - if hvac_mode == HVAC_MODE_AUTO: + if hvac_mode == HVACMode.AUTO: await self._device.set_control_mode(HMIP_AUTOMATIC_CM) else: await self._device.set_control_mode(HMIP_MANUAL_CM) @@ -238,7 +228,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): if preset_mode in self._device_profile_names: profile_idx = self._get_profile_idx_by_name(preset_mode) if self._device.controlMode != HMIP_AUTOMATIC_CM: - await self.async_set_hvac_mode(HVAC_MODE_AUTO) + await self.async_set_hvac_mode(HVACMode.AUTO) await self._device.set_active_profile(profile_idx) @property diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index a3537bff31b..3960dc1f9b9 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -60,7 +60,6 @@ class HomematicipAuth: async def get_auth(self, hass: HomeAssistant, hapid, pin): """Create a HomematicIP access point object.""" - # pylint: disable=no-self-use auth = AsyncAuth(hass.loop, async_get_clientsession(hass)) try: await auth.init(hapid) diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 457f2fdcd25..0cf02d88471 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -20,10 +20,9 @@ from homeassistant.components.light import ( ATTR_COLOR_NAME, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -70,6 +69,9 @@ async def async_setup_entry( class HomematicipLight(HomematicipGenericEntity, LightEntity): """Representation of the HomematicIP light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the light entity.""" super().__init__(hap, device) @@ -95,6 +97,9 @@ class HomematicipLightMeasuring(HomematicipLight): class HomematicipMultiDimmer(HomematicipGenericEntity, LightEntity): """Representation of HomematicIP Cloud dimmer.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__( self, hap: HomematicipHAP, @@ -120,11 +125,6 @@ class HomematicipMultiDimmer(HomematicipGenericEntity, LightEntity): (self._device.functionalChannels[self._channel].dimLevel or 0.0) * 255 ) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - async def async_turn_on(self, **kwargs) -> None: """Turn the dimmer on.""" if ATTR_BRIGHTNESS in kwargs: @@ -150,6 +150,10 @@ class HomematicipDimmer(HomematicipMultiDimmer, LightEntity): class HomematicipNotificationLight(HomematicipGenericEntity, LightEntity): """Representation of HomematicIP Cloud notification light.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + _attr_supported_features = LightEntityFeature.TRANSITION + def __init__(self, hap: HomematicipHAP, device, channel: int) -> None: """Initialize the notification light entity.""" if channel == 2: @@ -204,11 +208,6 @@ class HomematicipNotificationLight(HomematicipGenericEntity, LightEntity): return state_attr - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_TRANSITION - @property def unique_id(self) -> str: """Return a unique ID.""" diff --git a/homeassistant/components/homematicip_cloud/translations/fr.json b/homeassistant/components/homematicip_cloud/translations/fr.json index 01e1d1ed8c2..dd85878991f 100644 --- a/homeassistant/components/homematicip_cloud/translations/fr.json +++ b/homeassistant/components/homematicip_cloud/translations/fr.json @@ -6,7 +6,7 @@ "unknown": "Erreur inattendue" }, "error": { - "invalid_sgtin_or_pin": "Code SGTIN ou PIN invalide, veuillez r\u00e9essayer.", + "invalid_sgtin_or_pin": "Code SGTIN ou PIN non valide, veuillez r\u00e9essayer.", "press_the_button": "Veuillez appuyer sur le bouton bleu.", "register_failed": "\u00c9chec d'enregistrement. Veuillez r\u00e9essayer.", "timeout_button": "D\u00e9lai d'attente expir\u00e9, veuillez r\u00e9\u00e9ssayer." diff --git a/homeassistant/components/homematicip_cloud/translations/hu.json b/homeassistant/components/homematicip_cloud/translations/hu.json index 2915d442a37..975daa36126 100644 --- a/homeassistant/components/homematicip_cloud/translations/hu.json +++ b/homeassistant/components/homematicip_cloud/translations/hu.json @@ -15,7 +15,7 @@ "init": { "data": { "hapid": "Hozz\u00e1f\u00e9r\u00e9si pont azonos\u00edt\u00f3ja (SGTIN)", - "name": "N\u00e9v (opcion\u00e1lis, minden eszk\u00f6z n\u00e9vel\u0151tagjak\u00e9nt haszn\u00e1latos)", + "name": "Elnevez\u00e9s (opcion\u00e1lis, minden eszk\u00f6z n\u00e9vel\u0151tagjak\u00e9nt haszn\u00e1latos)", "pin": "PIN-k\u00f3d" }, "title": "V\u00e1lasszon HomematicIP hozz\u00e1f\u00e9r\u00e9si pontot" diff --git a/homeassistant/components/homematicip_cloud/translations/it.json b/homeassistant/components/homematicip_cloud/translations/it.json index 55c26532fb5..88a2dcd5fc8 100644 --- a/homeassistant/components/homematicip_cloud/translations/it.json +++ b/homeassistant/components/homematicip_cloud/translations/it.json @@ -6,7 +6,7 @@ "unknown": "Errore imprevisto" }, "error": { - "invalid_sgtin_or_pin": "SGTIN o Codice PIN non valido, si prega di riprovare.", + "invalid_sgtin_or_pin": "SGTIN o Codice PIN non valido, riprova.", "press_the_button": "Premi il pulsante blu.", "register_failed": "Registrazione non riuscita, riprova.", "timeout_button": "Timeout della pressione del pulsante blu, riprovare." diff --git a/homeassistant/components/homewizard/sensor.py b/homeassistant/components/homewizard/sensor.py index 8863d819db2..34479dd6b0a 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -5,16 +5,13 @@ import logging from typing import Final from homeassistant.components.sensor import ( - STATE_CLASS_MEASUREMENT, - STATE_CLASS_TOTAL_INCREASING, + SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - DEVICE_CLASS_ENERGY, - DEVICE_CLASS_GAS, - DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, PERCENTAGE, POWER_WATT, @@ -55,7 +52,7 @@ SENSORS: Final[tuple[SensorEntityDescription, ...]] = ( name="Wifi Strength", icon="mdi:wifi", native_unit_of_measurement=PERCENTAGE, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), @@ -63,64 +60,64 @@ SENSORS: Final[tuple[SensorEntityDescription, ...]] = ( key="total_power_import_t1_kwh", name="Total Power Import T1", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, ), SensorEntityDescription( key="total_power_import_t2_kwh", name="Total Power Import T2", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, ), SensorEntityDescription( key="total_power_export_t1_kwh", name="Total Power Export T1", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, ), SensorEntityDescription( key="total_power_export_t2_kwh", name="Total Power Export T2", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, ), SensorEntityDescription( key="active_power_w", name="Active Power", native_unit_of_measurement=POWER_WATT, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="active_power_l1_w", name="Active Power L1", native_unit_of_measurement=POWER_WATT, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="active_power_l2_w", name="Active Power L2", native_unit_of_measurement=POWER_WATT, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="active_power_l3_w", name="Active Power L3", native_unit_of_measurement=POWER_WATT, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="total_gas_m3", name="Total Gas", native_unit_of_measurement=VOLUME_CUBIC_METERS, - device_class=DEVICE_CLASS_GAS, - state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=SensorDeviceClass.GAS, + state_class=SensorStateClass.TOTAL_INCREASING, ), ) diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index a8c4fb2d5b5..0a8d7f2fb28 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -5,11 +5,7 @@ import logging from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -44,6 +40,9 @@ def setup_platform( class HomeworksLight(HomeworksDevice, LightEntity): """Homeworks Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, controller, addr, name, rate): """Create device with Addr, name, and rate.""" super().__init__(controller, addr, name) @@ -60,11 +59,6 @@ class HomeworksLight(HomeworksDevice, LightEntity): ) self._controller.request_dimmer_level(self._addr) - @property - def supported_features(self): - """Supported features.""" - return SUPPORT_BRIGHTNESS - def turn_on(self, **kwargs): """Turn on the light.""" if ATTR_BRIGHTNESS in kwargs: diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py index d141822e2ea..bad0ed96e01 100644 --- a/homeassistant/components/honeywell/__init__.py +++ b/homeassistant/components/honeywell/__init__.py @@ -21,7 +21,7 @@ from .const import ( UPDATE_LOOP_SLEEP_TIME = 5 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.CLIMATE, Platform.SENSOR] MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE} @@ -44,12 +44,12 @@ def _async_migrate_data_to_options( ) -async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Set up the Honeywell thermostat.""" - _async_migrate_data_to_options(hass, config) + _async_migrate_data_to_options(hass, config_entry) - username = config.data[CONF_USERNAME] - password = config.data[CONF_PASSWORD] + username = config_entry.data[CONF_USERNAME] + password = config_entry.data[CONF_PASSWORD] client = await hass.async_add_executor_job( get_somecomfort_client, username, password @@ -58,8 +58,8 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: if client is None: return False - loc_id = config.data.get(CONF_LOC_ID) - dev_id = config.data.get(CONF_DEV_ID) + loc_id = config_entry.data.get(CONF_LOC_ID) + dev_id = config_entry.data.get(CONF_DEV_ID) devices = {} @@ -73,31 +73,33 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: _LOGGER.debug("No devices found") return False - data = HoneywellData(hass, config, client, username, password, devices) + data = HoneywellData(hass, config_entry, client, username, password, devices) await data.async_update() hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][config.entry_id] = data - hass.config_entries.async_setup_platforms(config, PLATFORMS) + hass.data[DOMAIN][config_entry.entry_id] = data + hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) - config.async_on_unload(config.add_update_listener(update_listener)) + config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) return True -async def update_listener(hass: HomeAssistant, config: ConfigEntry) -> None: +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Update listener.""" - await hass.config_entries.async_reload(config.entry_id) + await hass.config_entries.async_reload(config_entry.entry_id) -async def async_unload_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload the config config and platforms.""" - unload_ok = await hass.config_entries.async_unload_platforms(config, PLATFORMS) + unload_ok = await hass.config_entries.async_unload_platforms( + config_entry, PLATFORMS + ) if unload_ok: hass.data.pop(DOMAIN) return unload_ok -def get_somecomfort_client(username, password): +def get_somecomfort_client(username: str, password: str) -> somecomfort.SomeComfort: """Initialize the somecomfort client.""" try: return somecomfort.SomeComfort(username, password) @@ -115,10 +117,18 @@ def get_somecomfort_client(username, password): class HoneywellData: """Get the latest data and update.""" - def __init__(self, hass, config, client, username, password, devices): + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + client: somecomfort.SomeComfort, + username: str, + password: str, + devices: dict[str, somecomfort.Device], + ) -> None: """Initialize the data object.""" self._hass = hass - self._config = config + self._config = config_entry self._client = client self._username = username self._password = password diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index f8c07de7184..5c238d73e0b 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -10,25 +10,16 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, + DEFAULT_MAX_TEMP, + DEFAULT_MIN_TEMP, FAN_AUTO, FAN_DIFFUSE, FAN_ON, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_NONE, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -46,23 +37,23 @@ ATTR_PERMANENT_HOLD = "permanent_hold" PRESET_HOLD = "Hold" HVAC_MODE_TO_HW_MODE = { - "SwitchOffAllowed": {HVAC_MODE_OFF: "off"}, - "SwitchAutoAllowed": {HVAC_MODE_HEAT_COOL: "auto"}, - "SwitchCoolAllowed": {HVAC_MODE_COOL: "cool"}, - "SwitchHeatAllowed": {HVAC_MODE_HEAT: "heat"}, + "SwitchOffAllowed": {HVACMode.OFF: "off"}, + "SwitchAutoAllowed": {HVACMode.HEAT_COOL: "auto"}, + "SwitchCoolAllowed": {HVACMode.COOL: "cool"}, + "SwitchHeatAllowed": {HVACMode.HEAT: "heat"}, } HW_MODE_TO_HVAC_MODE = { - "off": HVAC_MODE_OFF, - "emheat": HVAC_MODE_HEAT, - "heat": HVAC_MODE_HEAT, - "cool": HVAC_MODE_COOL, - "auto": HVAC_MODE_HEAT_COOL, + "off": HVACMode.OFF, + "emheat": HVACMode.HEAT, + "heat": HVACMode.HEAT, + "cool": HVACMode.COOL, + "auto": HVACMode.HEAT_COOL, } HW_MODE_TO_HA_HVAC_ACTION = { - "off": CURRENT_HVAC_IDLE, - "fan": CURRENT_HVAC_FAN, - "heat": CURRENT_HVAC_HEAT, - "cool": CURRENT_HVAC_COOL, + "off": HVACAction.IDLE, + "fan": HVACAction.FAN, + "heat": HVACAction.HEATING, + "cool": HVACAction.COOLING, } FAN_MODE_TO_HW = { "fanModeOnAllowed": {FAN_ON: "on"}, @@ -119,16 +110,16 @@ class HoneywellUSThermostat(ClimateEntity): self._attr_hvac_modes = list(self._hvac_mode_map) self._attr_supported_features = ( - SUPPORT_PRESET_MODE - | SUPPORT_TARGET_TEMPERATURE - | SUPPORT_TARGET_TEMPERATURE_RANGE + ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) if device._data["canControlHumidification"]: - self._attr_supported_features |= SUPPORT_TARGET_HUMIDITY + self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY if device.raw_ui_data["SwitchEmergencyHeatAllowed"]: - self._attr_supported_features |= SUPPORT_AUX_HEAT + self._attr_supported_features |= ClimateEntityFeature.AUX_HEAT if not device._data["hasFan"]: return @@ -139,12 +130,12 @@ class HoneywellUSThermostat(ClimateEntity): self._attr_fan_modes = list(self._fan_mode_map) - self._attr_supported_features |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE @property def extra_state_attributes(self) -> dict[str, Any]: """Return the device specific state attributes.""" - data = {} + data: dict[str, Any] = {} data[ATTR_FAN_ACTION] = "running" if self._device.fan_running else "idle" data[ATTR_PERMANENT_HOLD] = self._is_permanent_hold() if self._device.raw_dr_data: @@ -154,20 +145,20 @@ class HoneywellUSThermostat(ClimateEntity): @property def min_temp(self) -> float: """Return the minimum temperature.""" - if self.hvac_mode in [HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL]: + if self.hvac_mode in [HVACMode.COOL, HVACMode.HEAT_COOL]: return self._device.raw_ui_data["CoolLowerSetptLimit"] - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return self._device.raw_ui_data["HeatLowerSetptLimit"] - return None + return DEFAULT_MIN_TEMP @property def max_temp(self) -> float: """Return the maximum temperature.""" - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return self._device.raw_ui_data["CoolUpperSetptLimit"] - if self.hvac_mode in [HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL]: + if self.hvac_mode in [HVACMode.HEAT, HVACMode.HEAT_COOL]: return self._device.raw_ui_data["HeatUpperSetptLimit"] - return None + return DEFAULT_MAX_TEMP @property def current_humidity(self) -> int | None: @@ -175,14 +166,14 @@ class HoneywellUSThermostat(ClimateEntity): return self._device.current_humidity @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" return HW_MODE_TO_HVAC_MODE[self._device.system_mode] @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: return None return HW_MODE_TO_HA_HVAC_ACTION[self._device.equipment_output_status] @@ -194,23 +185,23 @@ class HoneywellUSThermostat(ClimateEntity): @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return self._device.setpoint_cool - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return self._device.setpoint_heat return None @property def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self._device.setpoint_cool return None @property def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self._device.setpoint_heat return None @@ -258,11 +249,11 @@ class HoneywellUSThermostat(ClimateEntity): def set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - if {HVAC_MODE_COOL, HVAC_MODE_HEAT} & set(self._hvac_mode_map): + if {HVACMode.COOL, HVACMode.HEAT} & set(self._hvac_mode_map): self._set_temperature(**kwargs) try: - if HVAC_MODE_HEAT_COOL in self._hvac_mode_map: + if HVACMode.HEAT_COOL in self._hvac_mode_map: if temperature := kwargs.get(ATTR_TARGET_TEMP_HIGH): self._device.setpoint_cool = temperature if temperature := kwargs.get(ATTR_TARGET_TEMP_LOW): @@ -274,7 +265,7 @@ class HoneywellUSThermostat(ClimateEntity): """Set new target fan mode.""" self._device.fan_mode = self._fan_mode_map[fan_mode] - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" self._device.system_mode = self._hvac_mode_map[hvac_mode] @@ -351,10 +342,10 @@ class HoneywellUSThermostat(ClimateEntity): def turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" - if HVAC_MODE_HEAT in self.hvac_modes: - self.set_hvac_mode(HVAC_MODE_HEAT) + if HVACMode.HEAT in self.hvac_modes: + self.set_hvac_mode(HVACMode.HEAT) else: - self.set_hvac_mode(HVAC_MODE_OFF) + self.set_hvac_mode(HVACMode.OFF) async def async_update(self): """Get the latest state from the service.""" diff --git a/homeassistant/components/honeywell/const.py b/homeassistant/components/honeywell/const.py index 08e7c3fc14e..94455d569cb 100644 --- a/homeassistant/components/honeywell/const.py +++ b/homeassistant/components/honeywell/const.py @@ -9,5 +9,7 @@ DEFAULT_COOL_AWAY_TEMPERATURE = 88 DEFAULT_HEAT_AWAY_TEMPERATURE = 61 CONF_DEV_ID = "thermostat" CONF_LOC_ID = "location" +TEMPERATURE_STATUS_KEY = "outdoor_temperature" +HUMIDITY_STATUS_KEY = "outdoor_humidity" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py new file mode 100644 index 00000000000..3a4e8f777b2 --- /dev/null +++ b/homeassistant/components/honeywell/sensor.py @@ -0,0 +1,98 @@ +"""Support for Honeywell (US) Total Connect Comfort sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from somecomfort import Device + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from .const import DOMAIN, HUMIDITY_STATUS_KEY, TEMPERATURE_STATUS_KEY + + +def _get_temperature_sensor_unit(device: Device) -> str: + """Get the correct temperature unit for the device.""" + return TEMP_CELSIUS if device.temperature_unit == "C" else TEMP_FAHRENHEIT + + +@dataclass +class HoneywellSensorEntityDescriptionMixin: + """Mixin for required keys.""" + + value_fn: Callable[[Device], Any] + unit_fn: Callable[[Device], Any] + + +@dataclass +class HoneywellSensorEntityDescription( + SensorEntityDescription, HoneywellSensorEntityDescriptionMixin +): + """Describes a Honeywell sensor entity.""" + + +SENSOR_TYPES: tuple[HoneywellSensorEntityDescription, ...] = ( + HoneywellSensorEntityDescription( + key=TEMPERATURE_STATUS_KEY, + name="Temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.outdoor_temperature, + unit_fn=_get_temperature_sensor_unit, + ), + HoneywellSensorEntityDescription( + key=HUMIDITY_STATUS_KEY, + name="Humidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda device: device.outdoor_humidity, + unit_fn=lambda device: PERCENTAGE, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Honeywell thermostat.""" + data = hass.data[DOMAIN][config_entry.entry_id] + sensors = [] + + for device in data.devices.values(): + for description in SENSOR_TYPES: + if getattr(device, description.key) is not None: + sensors.append(HoneywellSensor(device, description)) + + async_add_entities(sensors) + + +class HoneywellSensor(SensorEntity): + """Representation of a Honeywell US Outdoor Temperature Sensor.""" + + entity_description: HoneywellSensorEntityDescription + + def __init__(self, device, description): + """Initialize the outdoor temperature sensor.""" + self._device = device + self.entity_description = description + self._attr_unique_id = f"{device.deviceid}_{description.key}" + self._attr_name = f"{device.name} outdoor {description.device_class}" + self._attr_native_unit_of_measurement = description.unit_fn(device) + + @property + def native_value(self) -> StateType: + """Return the state.""" + return self.entity_description.value_fn(self._device) diff --git a/homeassistant/components/honeywell/translations/ca.json b/homeassistant/components/honeywell/translations/ca.json index 34da1b89f10..0830d657173 100644 --- a/homeassistant/components/honeywell/translations/ca.json +++ b/homeassistant/components/honeywell/translations/ca.json @@ -9,8 +9,18 @@ "password": "Contrasenya", "username": "Nom d'usuari" }, - "description": "Introdueix les credencials utilitzades per iniciar sessi\u00f3 a mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (EUA)" + "description": "Introdueix les credencials utilitzades per iniciar sessi\u00f3 a mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Temperatura freda, mode fora", + "away_heat_temperature": "Temperatura calenta, mode fora" + }, + "description": "Opcions addicionals de configuraci\u00f3 de Honeywell. Les temperatures es configuren en Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/de.json b/homeassistant/components/honeywell/translations/de.json index a146d442eef..6cbceffce51 100644 --- a/homeassistant/components/honeywell/translations/de.json +++ b/homeassistant/components/honeywell/translations/de.json @@ -9,8 +9,18 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Bitte gib die Anmeldedaten ein, mit denen du dich bei mytotalconnectcomfort.com anmeldest.", - "title": "Honeywell Total Connect Comfort (US)" + "description": "Bitte gib die Anmeldedaten ein, mit denen du dich bei mytotalconnectcomfort.com anmeldest." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Abwesend K\u00fchlungstemperatur", + "away_heat_temperature": "Abwesend Heiztemperatur" + }, + "description": "Zus\u00e4tzliche Honeywell-Konfigurationsoptionen. Die Temperaturen werden in Fahrenheit eingestellt." } } } diff --git a/homeassistant/components/honeywell/translations/el.json b/homeassistant/components/honeywell/translations/el.json index 7ad0c5b0181..b3fd654ded8 100644 --- a/homeassistant/components/honeywell/translations/el.json +++ b/homeassistant/components/honeywell/translations/el.json @@ -9,8 +9,18 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" }, - "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b1\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (\u0397\u03a0\u0391)" + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b1\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03c8\u03cd\u03be\u03b7\u03c2 \u03bc\u03b1\u03ba\u03c1\u03b9\u03ac", + "away_heat_temperature": "\u0398\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b1 \u03b8\u03b5\u03c1\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b1\u03ba\u03c1\u03b9\u03ac" + }, + "description": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 Honeywell. \u039f\u03b9 \u03b8\u03b5\u03c1\u03bc\u03bf\u03ba\u03c1\u03b1\u03c3\u03af\u03b5\u03c2 \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03b5 Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/en.json b/homeassistant/components/honeywell/translations/en.json index 168d3a5b93d..bf47a15be55 100644 --- a/homeassistant/components/honeywell/translations/en.json +++ b/homeassistant/components/honeywell/translations/en.json @@ -9,8 +9,7 @@ "password": "Password", "username": "Username" }, - "description": "Please enter the credentials used to log into mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (US)" + "description": "Please enter the credentials used to log into mytotalconnectcomfort.com." } } }, @@ -21,8 +20,7 @@ "away_cool_temperature": "Away cool temperature", "away_heat_temperature": "Away heat temperature" }, - "description": "Additional Honeywell config options. Temperatures are set in Fahrenheit.", - "title": "Honeywell Options" + "description": "Additional Honeywell config options. Temperatures are set in Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/es.json b/homeassistant/components/honeywell/translations/es.json index 9f6c562e888..bdae26b8794 100644 --- a/homeassistant/components/honeywell/translations/es.json +++ b/homeassistant/components/honeywell/translations/es.json @@ -9,8 +9,7 @@ "password": "Contrase\u00f1a", "username": "Usuario" }, - "description": "Por favor, introduzca las credenciales utilizadas para iniciar sesi\u00f3n en mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (US)" + "description": "Por favor, introduzca las credenciales utilizadas para iniciar sesi\u00f3n en mytotalconnectcomfort.com." } } } diff --git a/homeassistant/components/honeywell/translations/et.json b/homeassistant/components/honeywell/translations/et.json index 264a1efeca5..6673959ad31 100644 --- a/homeassistant/components/honeywell/translations/et.json +++ b/homeassistant/components/honeywell/translations/et.json @@ -9,8 +9,18 @@ "password": "Salas\u00f5na", "username": "Kasutajanimi" }, - "description": "Sisesta saidile mytotalconnectcomfort.com sisenemiseks kasutatav mandaat.", - "title": "Honeywell Total Connect Comfort (USA)" + "description": "Sisesta saidile mytotalconnectcomfort.com sisenemiseks kasutatav mandaat." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Eemaloleku jahutuse temperatuur", + "away_heat_temperature": "Eemaloleku k\u00fctte temperatuur" + }, + "description": "T\u00e4iendavad Honeywelli seadistusv\u00f5imalused. Temperatuurid on Fahrenheiti kraadides." } } } diff --git a/homeassistant/components/honeywell/translations/fr.json b/homeassistant/components/honeywell/translations/fr.json index ac11cf20576..0043e8990f1 100644 --- a/homeassistant/components/honeywell/translations/fr.json +++ b/homeassistant/components/honeywell/translations/fr.json @@ -9,8 +9,18 @@ "password": "Mot de passe", "username": "Nom d'utilisateur" }, - "description": "Veuillez saisir les informations d'identification utilis\u00e9es pour vous connecter \u00e0 mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (\u00c9tats-Unis)" + "description": "Veuillez saisir les informations d'identification utilis\u00e9es pour vous connecter \u00e0 mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Temp\u00e9rature de refroidissement en absence", + "away_heat_temperature": "Temp\u00e9rature de chauffage en absence" + }, + "description": "Options de configuration Honeywell suppl\u00e9mentaires. Les temp\u00e9ratures sont exprim\u00e9es en degr\u00e9s Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/hu.json b/homeassistant/components/honeywell/translations/hu.json index 5583dc22f2e..14ba9167ea5 100644 --- a/homeassistant/components/honeywell/translations/hu.json +++ b/homeassistant/components/honeywell/translations/hu.json @@ -9,8 +9,18 @@ "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "K\u00e9rj\u00fck, adja meg a mytotalconnectcomfort.com webhelyre val\u00f3 bejelentkez\u00e9shez haszn\u00e1lt hiteles\u00edt\u0151 adatokat.", - "title": "Honeywell Total Connect Comfort (USA)" + "description": "K\u00e9rj\u00fck, adja meg a mytotalconnectcomfort.com webhelyre val\u00f3 bejelentkez\u00e9shez haszn\u00e1lt hiteles\u00edt\u0151 adatokat." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "H\u0171t\u00e9si h\u0151m\u00e9r\u00e9s\u00e9klet t\u00e1voll\u00e9ti m\u00f3dban", + "away_heat_temperature": "F\u0171t\u00e9si h\u0151m\u00e9r\u00e9s\u00e9klet t\u00e1voll\u00e9ti m\u00f3dban" + }, + "description": "Tov\u00e1bbi Honeywell konfigur\u00e1ci\u00f3s lehet\u0151s\u00e9gek. A h\u0151m\u00e9rs\u00e9kletek Fahrenheitben vannak be\u00e1ll\u00edtva." } } } diff --git a/homeassistant/components/honeywell/translations/id.json b/homeassistant/components/honeywell/translations/id.json index 62151da1481..5ed95a27a76 100644 --- a/homeassistant/components/honeywell/translations/id.json +++ b/homeassistant/components/honeywell/translations/id.json @@ -9,8 +9,18 @@ "password": "Kata Sandi", "username": "Nama Pengguna" }, - "description": "Masukkan kredensial yang digunakan untuk masuk ke mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (AS)" + "description": "Masukkan kredensial yang digunakan untuk masuk ke mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Suhu dingin ketika jauh", + "away_heat_temperature": "Suhu panas ketika jauh" + }, + "description": "Opsi konfigurasi Honeywell tambahan. Suhu diatur dalam Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/it.json b/homeassistant/components/honeywell/translations/it.json index 52c828ddcde..87669762fa2 100644 --- a/homeassistant/components/honeywell/translations/it.json +++ b/homeassistant/components/honeywell/translations/it.json @@ -9,8 +9,18 @@ "password": "Password", "username": "Nome utente" }, - "description": "Inserisci le credenziali utilizzate per accedere a mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (USA)" + "description": "Inserisci le credenziali utilizzate per accedere a mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Fuori casa temperatura fresca", + "away_heat_temperature": "Fuori casa temperatura calda" + }, + "description": "Ulteriori opzioni di configurazione di Honeywell. Le temperature sono impostate in Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/ja.json b/homeassistant/components/honeywell/translations/ja.json index 1d3e19750c6..1af30341d33 100644 --- a/homeassistant/components/honeywell/translations/ja.json +++ b/homeassistant/components/honeywell/translations/ja.json @@ -9,8 +9,18 @@ "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "username": "\u30e6\u30fc\u30b6\u30fc\u540d" }, - "description": "mytotalconnectcomfort.com \u306b\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308b\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Honeywell Total Connect Comfort (US)" + "description": "mytotalconnectcomfort.com \u306b\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308b\u8a8d\u8a3c\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "\u30a2\u30a6\u30a7\u30a4\u30af\u30fc\u30eb(Away cool)\u6e29\u5ea6", + "away_heat_temperature": "\u30a2\u30a6\u30a7\u30a4\u30d2\u30fc\u30c8(Away heat)\u6e29\u5ea6" + }, + "description": "Honeywell\u306e\u8ffd\u52a0\u8a2d\u5b9a\u30aa\u30d7\u30b7\u30e7\u30f3\u3002\u6e29\u5ea6\u306f\u83ef\u6c0f(\u00b0F)\u3067\u8a2d\u5b9a\u3055\u308c\u307e\u3059\u3002" } } } diff --git a/homeassistant/components/honeywell/translations/nl.json b/homeassistant/components/honeywell/translations/nl.json index 0abd80fa088..9d82ef53b21 100644 --- a/homeassistant/components/honeywell/translations/nl.json +++ b/homeassistant/components/honeywell/translations/nl.json @@ -9,8 +9,18 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, - "description": "Voer de inloggegevens in die zijn gebruikt om in te loggen op mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (US)" + "description": "Voer de inloggegevens in die zijn gebruikt om in te loggen op mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Afwezig koeltemperatuur", + "away_heat_temperature": "Afwezig warmte temperatuur" + }, + "description": "Extra Honeywell configuratie opties. Temperaturen zijn ingesteld in Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/no.json b/homeassistant/components/honeywell/translations/no.json index 97d31d34961..e35e8a2b278 100644 --- a/homeassistant/components/honeywell/translations/no.json +++ b/homeassistant/components/honeywell/translations/no.json @@ -9,8 +9,18 @@ "password": "Passord", "username": "Brukernavn" }, - "description": "Vennligst skriv inn legitimasjonen som brukes for \u00e5 logge deg p\u00e5 mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (USA)" + "description": "Vennligst skriv inn legitimasjonen som brukes for \u00e5 logge deg p\u00e5 mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Borte kj\u00f8lig temperatur", + "away_heat_temperature": "Borte varmetemperatur" + }, + "description": "Ytterligere Honeywell-konfigurasjonsalternativer. Temperaturene er satt i Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/pl.json b/homeassistant/components/honeywell/translations/pl.json index c109565e33a..e484f88cb49 100644 --- a/homeassistant/components/honeywell/translations/pl.json +++ b/homeassistant/components/honeywell/translations/pl.json @@ -9,8 +9,18 @@ "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce u\u017cywane na mytotalconnectcomfort.com", - "title": "Honeywell Total Connect Comfort (USA)" + "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce u\u017cywane na mytotalconnectcomfort.com" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Temperatura ch\u0142odzenia w trybie \"poza domem\"", + "away_heat_temperature": "Temperatura grzania w trybie \"poza domem\"" + }, + "description": "Dodatkowe opcje konfiguracji Honeywell. Temperatury s\u0105 ustawiane w stopniach Fahrenheita." } } } diff --git a/homeassistant/components/honeywell/translations/pt-BR.json b/homeassistant/components/honeywell/translations/pt-BR.json index f16a6c71637..4cae96c5b1b 100644 --- a/homeassistant/components/honeywell/translations/pt-BR.json +++ b/homeassistant/components/honeywell/translations/pt-BR.json @@ -9,8 +9,18 @@ "password": "Senha", "username": "Usu\u00e1rio" }, - "description": "Insira as credenciais usadas para fazer login em mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (EUA)" + "description": "Insira as credenciais usadas para fazer login em mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Temperatura de frio ausente", + "away_heat_temperature": "Temperatura de calor ausente" + }, + "description": "Op\u00e7\u00f5es adicionais de configura\u00e7\u00e3o Honeywell. As temperaturas s\u00e3o definidas em Fahrenheit." } } } diff --git a/homeassistant/components/honeywell/translations/ru.json b/homeassistant/components/honeywell/translations/ru.json index 1d775e6c2c7..b370df892f6 100644 --- a/homeassistant/components/honeywell/translations/ru.json +++ b/homeassistant/components/honeywell/translations/ru.json @@ -9,8 +9,18 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u043d\u0430 mytotalconnectcomfort.com.", - "title": "Honeywell Total Connect Comfort (\u0421\u0428\u0410)" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u043d\u0430 mytotalconnectcomfort.com." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043e\u0445\u043b\u0430\u0436\u0434\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \"\u041d\u0435 \u0434\u043e\u043c\u0430\"", + "away_heat_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043d\u0430\u0433\u0440\u0435\u0432\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \"\u041d\u0435 \u0434\u043e\u043c\u0430\"" + }, + "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Honeywell. \u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0433\u0440\u0430\u0434\u0443\u0441\u0430\u0445 \u0424\u0430\u0440\u0435\u043d\u0433\u0435\u0439\u0442\u0430." } } } diff --git a/homeassistant/components/honeywell/translations/tr.json b/homeassistant/components/honeywell/translations/tr.json index e6eb57aca1f..d0c02e5029f 100644 --- a/homeassistant/components/honeywell/translations/tr.json +++ b/homeassistant/components/honeywell/translations/tr.json @@ -9,8 +9,18 @@ "password": "Parola", "username": "Kullan\u0131c\u0131 Ad\u0131" }, - "description": "L\u00fctfen mytotalconnectcomfort.com'da oturum a\u00e7mak i\u00e7in kullan\u0131lan kimlik bilgilerini girin.", - "title": "Honeywell Toplam Ba\u011flant\u0131 Konforu (ABD)" + "description": "L\u00fctfen mytotalconnectcomfort.com'da oturum a\u00e7mak i\u00e7in kullan\u0131lan kimlik bilgilerini girin." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "Uzak so\u011futma derecesi", + "away_heat_temperature": "Uzak \u0131s\u0131tma derecesi" + }, + "description": "Ek Honeywell yap\u0131land\u0131rma se\u00e7enekleri. S\u0131cakl\u0131klar Fahrenheit cinsinden ayarlan\u0131r." } } } diff --git a/homeassistant/components/honeywell/translations/zh-Hant.json b/homeassistant/components/honeywell/translations/zh-Hant.json index 906506d41a5..c6a657b13be 100644 --- a/homeassistant/components/honeywell/translations/zh-Hant.json +++ b/homeassistant/components/honeywell/translations/zh-Hant.json @@ -9,8 +9,18 @@ "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u8acb\u8f38\u5165\u767b\u5165 mytotalconnectcomfort.com \u4e4b\u6191\u8b49\u3002", - "title": "Honeywell Total Connect Comfort\uff08\u7f8e\u570b\uff09" + "description": "\u8acb\u8f38\u5165\u767b\u5165 mytotalconnectcomfort.com \u4e4b\u6191\u8b49\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "away_cool_temperature": "\u96e2\u5bb6\u5236\u51b7\u6eab\u5ea6", + "away_heat_temperature": "\u96e2\u5bb6\u52a0\u71b1\u6eab\u5ea6" + }, + "description": "\u9644\u52a0 Honeywell \u8a2d\u5b9a\u9078\u9805\u3002\u6eab\u5ea6\u4ee5\u83ef\u6c0f\u9032\u884c\u8a2d\u5b9a\u3002" } } } diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py index fd6b7b3d3cc..3cbe9ad3959 100644 --- a/homeassistant/components/horizon/media_player.py +++ b/homeassistant/components/horizon/media_player.py @@ -9,17 +9,12 @@ from horimote.exceptions import AuthenticationError import voluptuous as vol from homeassistant import util -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_CHANNEL from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -42,15 +37,6 @@ DEFAULT_PORT = 5900 MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) -SUPPORT_HORIZON = ( - SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF -) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -91,6 +77,16 @@ def setup_platform( class HorizonDevice(MediaPlayerEntity): """Representation of a Horizon HD Recorder.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + ) + def __init__(self, client, name, remote_keys): """Initialize the remote.""" self._client = client @@ -108,11 +104,6 @@ class HorizonDevice(MediaPlayerEntity): """Return the state of the device.""" return self._state - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_HORIZON - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update(self): """Update State using the media server running on the Horizon.""" diff --git a/homeassistant/components/huawei_lte/translations/hu.json b/homeassistant/components/huawei_lte/translations/hu.json index 36d08438fca..b3cc71d5a9d 100644 --- a/homeassistant/components/huawei_lte/translations/hu.json +++ b/homeassistant/components/huawei_lte/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "not_huawei_lte": "Nem Huawei LTE eszk\u00f6z" }, "error": { diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 265777814a8..3df17baad16 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -21,6 +21,7 @@ from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, device_registry import homeassistant.helpers.config_validation as cv +from homeassistant.util.network import is_ipv6_address from .const import ( CONF_ALLOW_HUE_GROUPS, @@ -230,6 +231,10 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if not url.hostname: return self.async_abort(reason="not_hue_bridge") + # Ignore if host is IPv6 + if is_ipv6_address(url.hostname): + return self.async_abort(reason="invalid_host") + # abort if we already have exactly this bridge id/host # reload the integration if the host got updated bridge_id = normalize_bridge_id(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]) @@ -251,6 +256,10 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by the Zeroconf component. It will check if the host is already configured and delegate to the import step if not. """ + # Ignore if host is IPv6 + if is_ipv6_address(discovery_info.host): + return self.async_abort(reason="invalid_host") + # abort if we already have exactly this bridge id/host # reload the integration if the host got updated bridge_id = normalize_bridge_id(discovery_info.properties["bridgeid"]) diff --git a/homeassistant/components/hue/strings.json b/homeassistant/components/hue/strings.json index 266f26016c4..0d7c67ec84b 100644 --- a/homeassistant/components/hue/strings.json +++ b/homeassistant/components/hue/strings.json @@ -30,7 +30,8 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", - "not_hue_bridge": "Not a Hue bridge" + "not_hue_bridge": "Not a Hue bridge", + "invalid_host": "Invalid host" } }, "device_automation": { diff --git a/homeassistant/components/hue/translations/ca.json b/homeassistant/components/hue/translations/ca.json index 92fb30b15d9..3b192e80b00 100644 --- a/homeassistant/components/hue/translations/ca.json +++ b/homeassistant/components/hue/translations/ca.json @@ -6,6 +6,7 @@ "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "cannot_connect": "Ha fallat la connexi\u00f3", "discover_timeout": "No s'han pogut descobrir enlla\u00e7os Hue", + "invalid_host": "Amfitri\u00f3 inv\u00e0lid", "no_bridges": "No s'han trobat enlla\u00e7os Philips Hue", "not_hue_bridge": "No \u00e9s un enlla\u00e7 Hue", "unknown": "Error inesperat" diff --git a/homeassistant/components/hue/translations/de.json b/homeassistant/components/hue/translations/de.json index 2f8485aae0c..c28014031cc 100644 --- a/homeassistant/components/hue/translations/de.json +++ b/homeassistant/components/hue/translations/de.json @@ -6,6 +6,7 @@ "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "cannot_connect": "Verbindung fehlgeschlagen", "discover_timeout": "Es k\u00f6nnen keine Hue Bridges erkannt werden", + "invalid_host": "Ung\u00fcltiger Host", "no_bridges": "Keine Philips Hue Bridges erkannt", "not_hue_bridge": "Keine Philips Hue Bridge entdeckt", "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/hue/translations/el.json b/homeassistant/components/hue/translations/el.json index 99bdc934929..7d1c2cabd44 100644 --- a/homeassistant/components/hue/translations/el.json +++ b/homeassistant/components/hue/translations/el.json @@ -6,6 +6,7 @@ "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "discover_timeout": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03bf\u03cd \u03b3\u03b5\u03c6\u03c5\u03c1\u03ce\u03bd Hue", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", "no_bridges": "\u0394\u03b5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03b3\u03ad\u03c6\u03c5\u03c1\u03b5\u03c2 Philips Hue", "not_hue_bridge": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03ad\u03c6\u03c5\u03c1\u03b1 Hue", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" diff --git a/homeassistant/components/hue/translations/en.json b/homeassistant/components/hue/translations/en.json index 7757aca9373..f617be431b9 100644 --- a/homeassistant/components/hue/translations/en.json +++ b/homeassistant/components/hue/translations/en.json @@ -6,6 +6,7 @@ "already_in_progress": "Configuration flow is already in progress", "cannot_connect": "Failed to connect", "discover_timeout": "Unable to discover Hue bridges", + "invalid_host": "Invalid host", "no_bridges": "No Philips Hue bridges discovered", "not_hue_bridge": "Not a Hue bridge", "unknown": "Unexpected error" diff --git a/homeassistant/components/hue/translations/et.json b/homeassistant/components/hue/translations/et.json index 748c5726dfe..df1288d415d 100644 --- a/homeassistant/components/hue/translations/et.json +++ b/homeassistant/components/hue/translations/et.json @@ -6,6 +6,7 @@ "already_in_progress": "Seadistamine on juba k\u00e4imas", "cannot_connect": "\u00dchendus nurjus", "discover_timeout": "Ei leia Philips Hue sildu", + "invalid_host": "Kehtetu host", "no_bridges": "Philips Hue sildu ei avastatud", "not_hue_bridge": "See pole Hue sild", "unknown": "Ilmnes tundmatu viga" diff --git a/homeassistant/components/hue/translations/fr.json b/homeassistant/components/hue/translations/fr.json index ec9d104aa65..cd07cff9fea 100644 --- a/homeassistant/components/hue/translations/fr.json +++ b/homeassistant/components/hue/translations/fr.json @@ -6,6 +6,7 @@ "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "\u00c9chec de connexion", "discover_timeout": "D\u00e9tection de ponts Philips Hue impossible", + "invalid_host": "H\u00f4te non valide", "no_bridges": "Aucun pont Philips Hue n'a \u00e9t\u00e9 d\u00e9couvert", "not_hue_bridge": "Pas de pont Hue", "unknown": "Erreur inattendue" diff --git a/homeassistant/components/hue/translations/hu.json b/homeassistant/components/hue/translations/hu.json index 02b8652b253..dd5c13c5f59 100644 --- a/homeassistant/components/hue/translations/hu.json +++ b/homeassistant/components/hue/translations/hu.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "M\u00e1r minden Philips Hue bridge konfigur\u00e1lt", "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "discover_timeout": "Nem tal\u00e1lhat\u00f3 a Hue bridge", + "invalid_host": "\u00c9rv\u00e9nytelen c\u00edm", "no_bridges": "Nem tal\u00e1lhat\u00f3 Philips Hue bridget", "not_hue_bridge": "Nem egy Hue Bridge", "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt" diff --git a/homeassistant/components/hue/translations/id.json b/homeassistant/components/hue/translations/id.json index d7178f6d135..d450adfb3c4 100644 --- a/homeassistant/components/hue/translations/id.json +++ b/homeassistant/components/hue/translations/id.json @@ -6,6 +6,7 @@ "already_in_progress": "Alur konfigurasi sedang berlangsung", "cannot_connect": "Gagal terhubung", "discover_timeout": "Tidak dapat menemukan bridge Hue", + "invalid_host": "Host tidak valid", "no_bridges": "Bridge Philips Hue tidak ditemukan", "not_hue_bridge": "Bukan bridge Hue", "unknown": "Kesalahan yang tidak diharapkan" diff --git a/homeassistant/components/hue/translations/it.json b/homeassistant/components/hue/translations/it.json index fdafef01f5b..0c1dfce4c1a 100644 --- a/homeassistant/components/hue/translations/it.json +++ b/homeassistant/components/hue/translations/it.json @@ -6,6 +6,7 @@ "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi", "discover_timeout": "Impossibile trovare i bridge Hue", + "invalid_host": "Host non valido", "no_bridges": "Nessun bridge di Philips Hue trovato", "not_hue_bridge": "Non \u00e8 un bridge Hue", "unknown": "Errore imprevisto" diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index a7d29534218..56bac6b89d8 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -6,6 +6,7 @@ "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken", "discover_timeout": "Hue bridges kunnen niet worden gevonden", + "invalid_host": "Ongeldige host", "no_bridges": "Geen Philips Hue bridges ontdekt", "not_hue_bridge": "Dit is geen Hue bridge", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/hue/translations/no.json b/homeassistant/components/hue/translations/no.json index 08c19a9cc51..9c18003aca0 100644 --- a/homeassistant/components/hue/translations/no.json +++ b/homeassistant/components/hue/translations/no.json @@ -6,6 +6,7 @@ "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Tilkobling mislyktes", "discover_timeout": "Kunne ikke oppdage Hue Bridger", + "invalid_host": "Ugyldig vert", "no_bridges": "Ingen Philips Hue Bridger oppdaget", "not_hue_bridge": "Ikke en Hue bro", "unknown": "Uventet feil" diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index 2bdcbc7e053..abfb6fa2a05 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -6,6 +6,7 @@ "already_in_progress": "Konfiguracja jest ju\u017c w toku", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "discover_timeout": "Nie mo\u017cna wykry\u0107 \u017cadnych mostk\u00f3w Hue", + "invalid_host": "Nieprawid\u0142owy host", "no_bridges": "Nie wykryto mostk\u00f3w Hue", "not_hue_bridge": "To nie jest mostek Hue", "unknown": "Nieoczekiwany b\u0142\u0105d" diff --git a/homeassistant/components/hue/translations/pt-BR.json b/homeassistant/components/hue/translations/pt-BR.json index 05dec678318..cc97e5e055e 100644 --- a/homeassistant/components/hue/translations/pt-BR.json +++ b/homeassistant/components/hue/translations/pt-BR.json @@ -6,6 +6,7 @@ "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "cannot_connect": "Falha ao conectar", "discover_timeout": "Incapaz de descobrir pontes Hue", + "invalid_host": "Host inv\u00e1lido", "no_bridges": "N\u00e3o h\u00e1 pontes Philips Hue descobertas", "not_hue_bridge": "N\u00e3o \u00e9 uma ponte Hue", "unknown": "Erro inesperado" diff --git a/homeassistant/components/hue/translations/ru.json b/homeassistant/components/hue/translations/ru.json index 8bd5ac77d52..6e470b74f1f 100644 --- a/homeassistant/components/hue/translations/ru.json +++ b/homeassistant/components/hue/translations/ru.json @@ -6,6 +6,7 @@ "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "discover_timeout": "\u0428\u043b\u044e\u0437 Philips Hue \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d.", + "invalid_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u043e\u0441\u0442.", "no_bridges": "\u0428\u043b\u044e\u0437\u044b Philips Hue \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", "not_hue_bridge": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0448\u043b\u044e\u0437\u043e\u043c Hue.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." diff --git a/homeassistant/components/hue/translations/tr.json b/homeassistant/components/hue/translations/tr.json index 8d23af54c66..32ab9bb1887 100644 --- a/homeassistant/components/hue/translations/tr.json +++ b/homeassistant/components/hue/translations/tr.json @@ -6,6 +6,7 @@ "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "cannot_connect": "Ba\u011flanma hatas\u0131", "discover_timeout": "Hue k\u00f6pr\u00fcleri bulunam\u0131yor", + "invalid_host": "Ge\u00e7ersiz sunucu", "no_bridges": "Philips Hue k\u00f6pr\u00fcs\u00fc bulunamad\u0131", "not_hue_bridge": "Hue k\u00f6pr\u00fcs\u00fc de\u011fil", "unknown": "Beklenmeyen hata" diff --git a/homeassistant/components/hue/translations/zh-Hant.json b/homeassistant/components/hue/translations/zh-Hant.json index 6c37c699340..1816b459a85 100644 --- a/homeassistant/components/hue/translations/zh-Hant.json +++ b/homeassistant/components/hue/translations/zh-Hant.json @@ -6,6 +6,7 @@ "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557", "discover_timeout": "\u7121\u6cd5\u641c\u5c0b\u5230 Hue Bridge", + "invalid_host": "\u4e3b\u6a5f\u7aef\u7121\u6548", "no_bridges": "\u672a\u767c\u73fe\u5230 Philips Hue Bridge", "not_hue_bridge": "\u975e Hue Bridge \u88dd\u7f6e", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 0eac6948790..8a6752307cc 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -14,13 +14,10 @@ from homeassistant.components.light import ( ATTR_FLASH, ATTR_TRANSITION, ATTR_XY_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_ONOFF, - COLOR_MODE_XY, FLASH_SHORT, SUPPORT_FLASH, SUPPORT_TRANSITION, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -204,7 +201,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity): @callback def _update_values(self) -> None: """Set base values from underlying lights of a group.""" - supported_color_modes = set() + supported_color_modes: set[ColorMode | str] = set() lights_with_color_support = 0 lights_with_color_temp_support = 0 lights_with_dimming_support = 0 @@ -241,18 +238,18 @@ class GroupedHueLight(HueBaseEntity, LightEntity): # this means that the state is derived from only some of the lights # and will never be 100% accurate but it will be close if lights_with_color_support > 0: - supported_color_modes.add(COLOR_MODE_XY) + supported_color_modes.add(ColorMode.XY) if lights_with_color_temp_support > 0: - supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + supported_color_modes.add(ColorMode.COLOR_TEMP) if lights_with_dimming_support > 0: if len(supported_color_modes) == 0: # only add color mode brightness if no color variants - supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + supported_color_modes.add(ColorMode.BRIGHTNESS) self._attr_brightness = round( ((total_brightness / lights_with_dimming_support) / 100) * 255 ) else: - supported_color_modes.add(COLOR_MODE_ONOFF) + supported_color_modes.add(ColorMode.ONOFF) self._dynamic_mode_active = lights_in_dynamic_mode > 0 self._attr_supported_color_modes = supported_color_modes # pick a winner for the current colormode @@ -260,10 +257,10 @@ class GroupedHueLight(HueBaseEntity, LightEntity): lights_with_color_temp_support > 0 and lights_in_colortemp_mode == lights_with_color_temp_support ): - self._attr_color_mode = COLOR_MODE_COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP elif lights_with_color_support > 0: - self._attr_color_mode = COLOR_MODE_XY + self._attr_color_mode = ColorMode.XY elif lights_with_dimming_support > 0: - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_color_mode = ColorMode.BRIGHTNESS else: - self._attr_color_mode = COLOR_MODE_ONOFF + self._attr_color_mode = ColorMode.ONOFF diff --git a/homeassistant/components/hue/v2/light.py b/homeassistant/components/hue/v2/light.py index af3dfa80ffc..aaf96a3ca17 100644 --- a/homeassistant/components/hue/v2/light.py +++ b/homeassistant/components/hue/v2/light.py @@ -16,14 +16,11 @@ from homeassistant.components.light import ( ATTR_FLASH, ATTR_TRANSITION, ATTR_XY_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_ONOFF, - COLOR_MODE_XY, FLASH_SHORT, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -80,15 +77,15 @@ class HueLight(HueBaseEntity, LightEntity): self._attr_supported_features |= SUPPORT_FLASH self.resource = resource self.controller = controller - self._supported_color_modes = set() + self._supported_color_modes: set[ColorMode | str] = set() if self.resource.supports_color: - self._supported_color_modes.add(COLOR_MODE_XY) + self._supported_color_modes.add(ColorMode.XY) if self.resource.supports_color_temperature: - self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + self._supported_color_modes.add(ColorMode.COLOR_TEMP) if self.resource.supports_dimming: if len(self._supported_color_modes) == 0: # only add color mode brightness if no color variants - self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._supported_color_modes.add(ColorMode.BRIGHTNESS) # support transition if brightness control self._attr_supported_features |= SUPPORT_TRANSITION # get list of supported effects (combine effects and timed_effects) @@ -121,18 +118,18 @@ class HueLight(HueBaseEntity, LightEntity): return self.resource.on.on @property - def color_mode(self) -> str | None: + def color_mode(self) -> ColorMode: """Return the color mode of the light.""" if color_temp := self.resource.color_temperature: # Hue lights return `mired_valid` to indicate CT is active if color_temp.mirek_valid and color_temp.mirek is not None: - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP if self.resource.supports_color: - return COLOR_MODE_XY + return ColorMode.XY if self.resource.supports_dimming: - return COLOR_MODE_BRIGHTNESS + return ColorMode.BRIGHTNESS # fallback to on_off - return COLOR_MODE_ONOFF + return ColorMode.ONOFF @property def xy_color(self) -> tuple[float, float] | None: diff --git a/homeassistant/components/huisbaasje/config_flow.py b/homeassistant/components/huisbaasje/config_flow.py index fc686f25809..4139b0d75c5 100644 --- a/homeassistant/components/huisbaasje/config_flow.py +++ b/homeassistant/components/huisbaasje/config_flow.py @@ -69,7 +69,6 @@ class HuisbaasjeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): Data has the keys from DATA_SCHEMA with values provided by the user. """ - # pylint: disable=no-self-use username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 8eda2589417..b5f02a6a5d3 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -41,6 +41,7 @@ from .const import ( # noqa: F401 SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, SUPPORT_MODES, + HumidifierEntityFeature, ) _LOGGER = logging.getLogger(__name__) @@ -88,7 +89,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: SERVICE_SET_MODE, {vol.Required(ATTR_MODE): cv.string}, "async_set_mode", - [SUPPORT_MODES], + [HumidifierEntityFeature.MODES], ) component.async_register_entity_service( SERVICE_SET_HUMIDITY, @@ -142,7 +143,7 @@ class HumidifierEntity(ToggleEntity): ATTR_MAX_HUMIDITY: self.max_humidity, } - if supported_features & SUPPORT_MODES: + if supported_features & HumidifierEntityFeature.MODES: data[ATTR_AVAILABLE_MODES] = self.available_modes return data @@ -166,7 +167,7 @@ class HumidifierEntity(ToggleEntity): if self.target_humidity is not None: data[ATTR_HUMIDITY] = self.target_humidity - if supported_features & SUPPORT_MODES: + if supported_features & HumidifierEntityFeature.MODES: data[ATTR_MODE] = self.mode return data @@ -180,7 +181,7 @@ class HumidifierEntity(ToggleEntity): def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. - Requires SUPPORT_MODES. + Requires HumidifierEntityFeature.MODES. """ return self._attr_mode @@ -188,7 +189,7 @@ class HumidifierEntity(ToggleEntity): def available_modes(self) -> list[str] | None: """Return a list of available modes. - Requires SUPPORT_MODES. + Requires HumidifierEntityFeature.MODES. """ return self._attr_available_modes diff --git a/homeassistant/components/humidifier/const.py b/homeassistant/components/humidifier/const.py index a3df2756af9..03f89f5489a 100644 --- a/homeassistant/components/humidifier/const.py +++ b/homeassistant/components/humidifier/const.py @@ -1,4 +1,6 @@ """Provides the constants needed for component.""" +from enum import IntEnum + MODE_NORMAL = "normal" MODE_ECO = "eco" MODE_AWAY = "away" @@ -27,4 +29,13 @@ DEVICE_CLASS_DEHUMIDIFIER = "dehumidifier" SERVICE_SET_MODE = "set_mode" SERVICE_SET_HUMIDITY = "set_humidity" + +class HumidifierEntityFeature(IntEnum): + """Supported features of the alarm control panel entity.""" + + MODES = 1 + + +# The SUPPORT_MODES constant is deprecated as of Home Assistant 2022.5. +# Please use the HumidifierEntityFeature enum instead. SUPPORT_MODES = 1 diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index 5ff79bdf2be..db51ab34baa 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -66,7 +66,7 @@ async def async_get_actions( } actions.append({**base_action, CONF_TYPE: "set_humidity"}) - if supported_features & const.SUPPORT_MODES: + if supported_features & const.HumidifierEntityFeature.MODES: actions.append({**base_action, CONF_TYPE: "set_mode"}) return actions diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 323d199cfc6..c58c247d569 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -51,7 +51,7 @@ async def async_get_conditions( supported_features = get_supported_features(hass, entry.entity_id) - if supported_features & const.SUPPORT_MODES: + if supported_features & const.HumidifierEntityFeature.MODES: conditions.append( { CONF_CONDITION: "device", diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py index d9ecafbc537..aeeb18cceec 100644 --- a/homeassistant/components/humidifier/intent.py +++ b/homeassistant/components/humidifier/intent.py @@ -13,7 +13,7 @@ from . import ( SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, SERVICE_TURN_ON, - SUPPORT_MODES, + HumidifierEntityFeature, ) INTENT_HUMIDITY = "HassHumidifierSetpoint" @@ -90,7 +90,7 @@ class SetModeHandler(intent.IntentHandler): service_data = {ATTR_ENTITY_ID: state.entity_id} - intent.async_test_feature(state, SUPPORT_MODES, "modes") + intent.async_test_feature(state, HumidifierEntityFeature.MODES, "modes") mode = slots["mode"]["value"] if mode not in state.attributes.get(ATTR_AVAILABLE_MODES, []): diff --git a/homeassistant/components/humidifier/translations/hu.json b/homeassistant/components/humidifier/translations/hu.json index d8e2ce86a9a..7979334429d 100644 --- a/homeassistant/components/humidifier/translations/hu.json +++ b/homeassistant/components/humidifier/translations/hu.json @@ -13,6 +13,7 @@ "is_on": "{entity_name} be van kapcsolva" }, "trigger_type": { + "changed_states": "{entity_name} be- vagy kikapcsolt", "target_humidity_changed": "{name} k\u00edv\u00e1nt p\u00e1ratartalom megv\u00e1ltozott", "toggled": "{entity_name} \u00e1tkapcsolt", "turned_off": "{entity_name} ki lett kapcsolva", diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 32d78c5c8dd..2ef1cc46adf 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -14,12 +14,9 @@ import async_timeout from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -117,6 +114,13 @@ class PowerViewShade(ShadeEntity, CoverEntity): self._last_action_timestamp = 0 self._scheduled_transition_update = None self._current_cover_position = MIN_POSITION + self._attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + if self._device_info[DEVICE_MODEL] != LEGACY_DEVICE_MODEL: + self._attr_supported_features |= CoverEntityFeature.STOP self._forced_resync = None @property @@ -124,14 +128,6 @@ class PowerViewShade(ShadeEntity, CoverEntity): """Return the state attributes.""" return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name} - @property - def supported_features(self): - """Flag supported features.""" - supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - if self._device_info[DEVICE_MODEL] != LEGACY_DEVICE_MODEL: - supported_features |= SUPPORT_STOP - return supported_features - @property def is_closed(self): """Return if the cover is closed.""" diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 8b809bdec8a..fc4ec4d023e 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -13,10 +13,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -74,8 +73,6 @@ DEFAULT_PORT = const.DEFAULT_PORT_JSON DEFAULT_HDMI_PRIORITY = 880 DEFAULT_EFFECT_LIST: list[str] = [] -SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT - ICON_LIGHTBULB = "mdi:lightbulb" ICON_EFFECT = "mdi:lava-lamp" ICON_EXTERNAL_SOURCE = "mdi:television-ambient-light" @@ -127,6 +124,10 @@ async def async_setup_entry( class HyperionBaseLight(LightEntity): """A Hyperion light base class.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + _attr_supported_features = LightEntityFeature.EFFECT + def __init__( self, server_id: str, @@ -221,11 +222,6 @@ class HyperionBaseLight(LightEntity): """Return the list of supported effects.""" return self._effect_list - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_HYPERION - @property def available(self) -> bool: """Return server availability.""" @@ -489,7 +485,6 @@ class HyperionBaseLight(LightEntity): priority: dict[str, Any] | None = self._client.visible_priority return priority - # pylint: disable=no-self-use def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool: """Determine whether to allow a priority to update internal state.""" return True diff --git a/homeassistant/components/hyperion/translations/hu.json b/homeassistant/components/hyperion/translations/hu.json index 750f2380624..aaffa705279 100644 --- a/homeassistant/components/hyperion/translations/hu.json +++ b/homeassistant/components/hyperion/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "auth_new_token_not_granted_error": "Az \u00fajonnan l\u00e9trehozott tokent nem hagyt\u00e1k j\u00f3v\u00e1 a Hyperion felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9n", "auth_new_token_not_work_error": "Nem siker\u00fclt hiteles\u00edteni az \u00fajonnan l\u00e9trehozott token haszn\u00e1lat\u00e1val", "auth_required_error": "Nem siker\u00fclt meghat\u00e1rozni, hogy sz\u00fcks\u00e9ges-e enged\u00e9ly", @@ -27,7 +27,7 @@ "title": "Er\u0151s\u00edtse meg a Hyperion Ambilight szolg\u00e1ltat\u00e1s hozz\u00e1ad\u00e1s\u00e1t" }, "create_token": { - "description": "Az al\u00e1bbiakban v\u00e1lassza a **K\u00fcld\u00e9s** lehet\u0151s\u00e9get \u00faj hiteles\u00edt\u00e9si token k\u00e9r\u00e9s\u00e9hez. A k\u00e9relem j\u00f3v\u00e1hagy\u00e1s\u00e1hoz \u00e1tir\u00e1ny\u00edtunk a Hyperion felhaszn\u00e1l\u00f3i fel\u00fcletre. K\u00e9rj\u00fck, ellen\u0151rizze, hogy a megjelen\u00edtett azonos\u00edt\u00f3 \"{auth_id}\"", + "description": "Az al\u00e1bbiakban v\u00e1lassza a **Mehet** lehet\u0151s\u00e9get \u00faj hiteles\u00edt\u00e9si token k\u00e9r\u00e9s\u00e9hez. A k\u00e9relem j\u00f3v\u00e1hagy\u00e1s\u00e1hoz \u00e1tir\u00e1ny\u00edtunk a Hyperion felhaszn\u00e1l\u00f3i fel\u00fcletre. K\u00e9rj\u00fck, ellen\u0151rizze, hogy a megjelen\u00edtett azonos\u00edt\u00f3 \"{auth_id}\"", "title": "\u00daj hiteles\u00edt\u00e9si token automatikus l\u00e9trehoz\u00e1sa" }, "create_token_external": { diff --git a/homeassistant/components/ialarm/alarm_control_panel.py b/homeassistant/components/ialarm/alarm_control_panel.py index acb82af5658..be53eb99525 100644 --- a/homeassistant/components/ialarm/alarm_control_panel.py +++ b/homeassistant/components/ialarm/alarm_control_panel.py @@ -1,8 +1,7 @@ """Interfaces with iAlarm control panels.""" -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -24,6 +23,11 @@ async def async_setup_entry( class IAlarmPanel(CoordinatorEntity, AlarmControlPanelEntity): """Representation of an iAlarm device.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + @property def device_info(self) -> DeviceInfo: """Return device info for this device.""" @@ -48,11 +52,6 @@ class IAlarmPanel(CoordinatorEntity, AlarmControlPanelEntity): """Return the state of the device.""" return self.coordinator.state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - def alarm_disarm(self, code=None): """Send disarm command.""" self.coordinator.ialarm.disarm() diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index 08a2bee5f7e..7ccf0510ae2 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -13,10 +13,9 @@ from iaqualink.device import AqualinkHeater, AqualinkPump, AqualinkSensor, Aqual from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - DOMAIN, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, + DOMAIN as CLIMATE_DOMAIN, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -24,7 +23,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import AqualinkEntity, refresh_system -from .const import CLIMATE_SUPPORTED_MODES, DOMAIN as AQUALINK_DOMAIN +from .const import DOMAIN as AQUALINK_DOMAIN from .utils import await_or_reraise _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ async def async_setup_entry( ) -> None: """Set up discovered switches.""" devs = [] - for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]: + for dev in hass.data[AQUALINK_DOMAIN][CLIMATE_DOMAIN]: devs.append(HassAqualinkThermostat(dev)) async_add_entities(devs, True) @@ -47,21 +46,14 @@ async def async_setup_entry( class HassAqualinkThermostat(AqualinkEntity, ClimateEntity): """Representation of a thermostat.""" + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + @property def name(self) -> str: """Return the name of the thermostat.""" return self.dev.label.split(" ")[0] - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE - - @property - def hvac_modes(self) -> list[str]: - """Return the list of supported HVAC modes.""" - return CLIMATE_SUPPORTED_MODES - @property def pump(self) -> AqualinkPump: """Return the pump device for the current thermostat.""" @@ -69,19 +61,19 @@ class HassAqualinkThermostat(AqualinkEntity, ClimateEntity): return self.dev.system.devices[pump] @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return the current HVAC mode.""" state = AqualinkState(self.heater.state) if state == AqualinkState.ON: - return HVAC_MODE_HEAT - return HVAC_MODE_OFF + return HVACMode.HEAT + return HVACMode.OFF @refresh_system - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Turn the underlying heater switch on or off.""" - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: await await_or_reraise(self.heater.turn_on()) - elif hvac_mode == HVAC_MODE_OFF: + elif hvac_mode == HVACMode.OFF: await await_or_reraise(self.heater.turn_off()) else: _LOGGER.warning("Unknown operation mode: %s", hvac_mode) diff --git a/homeassistant/components/iaqualink/const.py b/homeassistant/components/iaqualink/const.py index 219eb912994..189d7083b2d 100644 --- a/homeassistant/components/iaqualink/const.py +++ b/homeassistant/components/iaqualink/const.py @@ -1,8 +1,5 @@ """Constants for the the iaqualink component.""" from datetime import timedelta -from homeassistant.components.climate.const import HVAC_MODE_HEAT, HVAC_MODE_OFF - DOMAIN = "iaqualink" -CLIMATE_SUPPORTED_MODES = [HVAC_MODE_HEAT, HVAC_MODE_OFF] UPDATE_INTERVAL = timedelta(seconds=30) diff --git a/homeassistant/components/iaqualink/light.py b/homeassistant/components/iaqualink/light.py index 7005606a393..505ad8da0cc 100644 --- a/homeassistant/components/iaqualink/light.py +++ b/homeassistant/components/iaqualink/light.py @@ -1,13 +1,13 @@ """Support for Aqualink pool lights.""" -import logging +from __future__ import annotations from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, DOMAIN, - SUPPORT_BRIGHTNESS, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -17,8 +17,6 @@ from . import AqualinkEntity, refresh_system from .const import DOMAIN as AQUALINK_DOMAIN from .utils import await_or_reraise -_LOGGER = logging.getLogger(__name__) - PARALLEL_UPDATES = 0 @@ -87,13 +85,22 @@ class HassAqualinkLight(AqualinkEntity, LightEntity): """Return supported light effects.""" return list(self.dev.supported_light_effects) + @property + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if self.dev.is_dimmer: + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[str] | None: + """Flag supported color modes.""" + return {self.color_mode} + @property def supported_features(self) -> int: """Return the list of features supported by the light.""" - if self.dev.is_dimmer: - return SUPPORT_BRIGHTNESS - if self.dev.is_color: - return SUPPORT_EFFECT + return LightEntityFeature.EFFECT return 0 diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py index 15591872248..ebab2592403 100644 --- a/homeassistant/components/ifttt/alarm_control_panel.py +++ b/homeassistant/components/ifttt/alarm_control_panel.py @@ -7,15 +7,10 @@ import re import voluptuous as vol from homeassistant.components.alarm_control_panel import ( - FORMAT_NUMBER, - FORMAT_TEXT, PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -132,6 +127,12 @@ def setup_platform( class IFTTTAlarmPanel(AlarmControlPanelEntity): """Representation of an alarm control panel controlled through IFTTT.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + ) + def __init__( self, name, @@ -164,11 +165,6 @@ class IFTTTAlarmPanel(AlarmControlPanelEntity): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - @property def assumed_state(self): """Notify that this platform return an assumed state.""" @@ -180,8 +176,8 @@ class IFTTTAlarmPanel(AlarmControlPanelEntity): if self._code is None: return None if isinstance(self._code, str) and re.search("^\\d+$", self._code): - return FORMAT_NUMBER - return FORMAT_TEXT + return CodeFormat.NUMBER + return CodeFormat.TEXT def alarm_disarm(self, code=None): """Send disarm command.""" diff --git a/homeassistant/components/ifttt/translations/ja.json b/homeassistant/components/ifttt/translations/ja.json index a3a63f532cf..87844be51d4 100644 --- a/homeassistant/components/ifttt/translations/ja.json +++ b/homeassistant/components/ifttt/translations/ja.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, "create_entry": { - "default": "Home Assistant\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u3059\u308b\u306b\u306f\u3001[IFTTT Webhook applet]({applet_url})\u306e\"Make a web request\"\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u4f7f\u7528\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u4ee5\u4e0b\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n- URL: `{webhook_url}`\n- Method(\u65b9\u5f0f): POST\n- Content Type: application/json\n\n\u53d7\u4fe1\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8]({docs_url})\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + "default": "Home Assistant\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u3059\u308b\u306b\u306f\u3001[IFTTT Webhook applet]({applet_url})\u306e\"Make a web request\"\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u4f7f\u7528\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u4ee5\u4e0b\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n- URL: `{webhook_url}`\n- Method(\u65b9\u5f0f): POST\n- Content Type(\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e): application/json\n\n\u53d7\u4fe1\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8]({docs_url})\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "step": { "user": { diff --git a/homeassistant/components/iglo/light.py b/homeassistant/components/iglo/light.py index 71dd34af032..39e1a5986ab 100644 --- a/homeassistant/components/iglo/light.py +++ b/homeassistant/components/iglo/light.py @@ -4,6 +4,7 @@ from __future__ import annotations import math from iglo import Lamp +from iglo.lamp import MODE_WHITE import voluptuous as vol from homeassistant.components.light import ( @@ -12,11 +13,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant @@ -53,6 +52,9 @@ def setup_platform( class IGloLamp(LightEntity): """Representation of an iGlo light.""" + _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} + _attr_supported_features = LightEntityFeature.EFFECT + def __init__(self, name, host, port): """Initialize the light.""" @@ -69,6 +71,15 @@ class IGloLamp(LightEntity): """Return the brightness of this light between 0..255.""" return int((self._lamp.state()["brightness"] / 200.0) * 255) + @property + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if self._lamp.state()["mode"] == MODE_WHITE: + return ColorMode.COLOR_TEMP + # The iglo library reports MODE_WHITE when an effect is active, this is not + # supported by Home Assistant, just report ColorMode.HS + return ColorMode.HS + @property def color_temp(self): """Return the color temperature.""" @@ -103,11 +114,6 @@ class IGloLamp(LightEntity): """Return the list of supported effects.""" return self._lamp.effect_list() - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | SUPPORT_EFFECT - @property def is_on(self): """Return true if light is on.""" diff --git a/homeassistant/components/ihc/light.py b/homeassistant/components/ihc/light.py index b86f9fb3c8a..caceaf9737c 100644 --- a/homeassistant/components/ihc/light.py +++ b/homeassistant/components/ihc/light.py @@ -3,11 +3,7 @@ from __future__ import annotations from ihcsdk.ihccontroller import IHCController -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -78,6 +74,12 @@ class IhcLight(IHCDevice, LightEntity): self._dimmable = dimmable self._state = False + if self._dimmable: + self._attr_color_mode = ColorMode.BRIGHTNESS + else: + self._attr_color_mode = ColorMode.ONOFF + self._attr_supported_color_modes = {self._attr_color_mode} + @property def brightness(self) -> int: """Return the brightness of this light between 0..255.""" @@ -88,13 +90,6 @@ class IhcLight(IHCDevice, LightEntity): """Return true if light is on.""" return self._state - @property - def supported_features(self): - """Flag supported features.""" - if self._dimmable: - return SUPPORT_BRIGHTNESS - return 0 - async def async_turn_on(self, **kwargs): """Turn the light on.""" if ATTR_BRIGHTNESS in kwargs: diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 2363b124e43..4ddba8f31e0 100644 --- a/homeassistant/components/image/manifest.json +++ b/homeassistant/components/image/manifest.json @@ -3,7 +3,7 @@ "name": "Image", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/image", - "requirements": ["pillow==9.0.1"], + "requirements": ["pillow==9.1.0"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index 94224d6d67e..c76f16093ee 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -4,10 +4,7 @@ from __future__ import annotations from typing import Any from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -37,6 +34,10 @@ async def async_setup_platform( class InComfortClimate(IncomfortChild, ClimateEntity): """Representation of an InComfort/InTouch climate device.""" + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__(self, client, heater, room) -> None: """Initialize the climate device.""" super().__init__() @@ -58,16 +59,6 @@ class InComfortClimate(IncomfortChild, ClimateEntity): """Return the unit of measurement.""" return TEMP_CELSIUS - @property - def hvac_mode(self) -> str: - """Return hvac operation ie. heat, cool mode.""" - return HVAC_MODE_HEAT - - @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes.""" - return [HVAC_MODE_HEAT] - @property def current_temperature(self) -> float | None: """Return the current temperature.""" @@ -78,11 +69,6 @@ class InComfortClimate(IncomfortChild, ClimateEntity): """Return the temperature we try to reach.""" return self._room.setpoint - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE - @property def min_temp(self) -> float: """Return max valid temperature that can be set.""" @@ -98,5 +84,5 @@ class InComfortClimate(IncomfortChild, ClimateEntity): temperature = kwargs.get(ATTR_TEMPERATURE) await self._room.set_override(temperature) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" diff --git a/homeassistant/components/influxdb/manifest.json b/homeassistant/components/influxdb/manifest.json index df2feab5146..a751c84b650 100644 --- a/homeassistant/components/influxdb/manifest.json +++ b/homeassistant/components/influxdb/manifest.json @@ -3,7 +3,7 @@ "name": "InfluxDB", "documentation": "https://www.home-assistant.io/integrations/influxdb", "requirements": ["influxdb==5.3.1", "influxdb-client==1.24.0"], - "codeowners": ["@fabaff", "@mdegat01"], + "codeowners": ["@mdegat01"], "iot_class": "local_push", "loggers": ["influxdb", "influxdb_client"] } diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index f198d552781..7dee3614ad5 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -22,6 +22,9 @@ from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.helpers.service from homeassistant.helpers.storage import Store @@ -86,6 +89,11 @@ def is_on(hass: HomeAssistant, entity_id: str) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input boolean.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( diff --git a/homeassistant/components/input_boolean/recorder.py b/homeassistant/components/input_boolean/recorder.py new file mode 100644 index 00000000000..8e94dc93f3b --- /dev/null +++ b/homeassistant/components/input_boolean/recorder.py @@ -0,0 +1,11 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.const import ATTR_EDITABLE +from homeassistant.core import HomeAssistant, callback + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude editable hint from being recorded in the database.""" + return {ATTR_EDITABLE} diff --git a/homeassistant/components/input_button/__init__.py b/homeassistant/components/input_button/__init__.py index c92f073971a..3182e36d5fc 100644 --- a/homeassistant/components/input_button/__init__.py +++ b/homeassistant/components/input_button/__init__.py @@ -18,6 +18,9 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.helpers.service from homeassistant.helpers.storage import Store @@ -71,6 +74,11 @@ class InputButtonStorageCollection(collection.StorageCollection): async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input button.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( diff --git a/homeassistant/components/input_button/recorder.py b/homeassistant/components/input_button/recorder.py new file mode 100644 index 00000000000..8e94dc93f3b --- /dev/null +++ b/homeassistant/components/input_button/recorder.py @@ -0,0 +1,11 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.const import ATTR_EDITABLE +from homeassistant.core import HomeAssistant, callback + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude editable hint from being recorded in the database.""" + return {ATTR_EDITABLE} diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index c25680b7180..fe034a8edb5 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -19,6 +19,9 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.helpers.service from homeassistant.helpers.storage import Store @@ -125,6 +128,11 @@ RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input datetime.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( diff --git a/homeassistant/components/input_datetime/recorder.py b/homeassistant/components/input_datetime/recorder.py new file mode 100644 index 00000000000..91c33ee0811 --- /dev/null +++ b/homeassistant/components/input_datetime/recorder.py @@ -0,0 +1,13 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.const import ATTR_EDITABLE +from homeassistant.core import HomeAssistant, callback + +from . import CONF_HAS_DATE, CONF_HAS_TIME + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude some attributes from being recorded in the database.""" + return {ATTR_EDITABLE, CONF_HAS_DATE, CONF_HAS_TIME} diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 491b9abc024..a6ee8dd0f7d 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -19,6 +19,9 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.helpers.service from homeassistant.helpers.storage import Store @@ -115,6 +118,11 @@ STORAGE_VERSION = 1 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input slider.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( diff --git a/homeassistant/components/input_number/recorder.py b/homeassistant/components/input_number/recorder.py new file mode 100644 index 00000000000..05a5023be0b --- /dev/null +++ b/homeassistant/components/input_number/recorder.py @@ -0,0 +1,19 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.const import ATTR_EDITABLE +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_STEP + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude editable hint from being recorded in the database.""" + return { + ATTR_EDITABLE, + ATTR_MAX, + ATTR_MIN, + ATTR_MODE, + ATTR_STEP, + } diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index ae5fc9d251e..83d6684a366 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -20,6 +20,9 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.helpers.service from homeassistant.helpers.storage import Store @@ -138,6 +141,11 @@ class InputSelectStore(Store): async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input select.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( diff --git a/homeassistant/components/input_select/recorder.py b/homeassistant/components/input_select/recorder.py new file mode 100644 index 00000000000..8e94dc93f3b --- /dev/null +++ b/homeassistant/components/input_select/recorder.py @@ -0,0 +1,11 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.const import ATTR_EDITABLE +from homeassistant.core import HomeAssistant, callback + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude editable hint from being recorded in the database.""" + return {ATTR_EDITABLE} diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 417dbf81249..f1e0b45afec 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -19,6 +19,9 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import collection import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.helpers.service from homeassistant.helpers.storage import Store @@ -115,6 +118,11 @@ RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input text.""" component = EntityComponent(_LOGGER, DOMAIN, hass) + + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( diff --git a/homeassistant/components/input_text/recorder.py b/homeassistant/components/input_text/recorder.py new file mode 100644 index 00000000000..0f4969270d0 --- /dev/null +++ b/homeassistant/components/input_text/recorder.py @@ -0,0 +1,19 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.const import ATTR_EDITABLE +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_PATTERN + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude editable hint from being recorded in the database.""" + return { + ATTR_EDITABLE, + ATTR_MAX, + ATTR_MIN, + ATTR_MODE, + ATTR_PATTERN, + } diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index 0f17e1231e4..e2bebf0bb7f 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -4,16 +4,19 @@ from contextlib import suppress import logging from pyinsteon import async_close, async_connect, devices +from pyinsteon.constants import ReadWriteMode from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import ConfigType from . import api from .const import ( CONF_CAT, + CONF_DEV_PATH, CONF_DIM_STEPS, CONF_HOUSECODE, CONF_OVERRIDE, @@ -46,7 +49,8 @@ async def async_get_device_config(hass, config_entry): with suppress(AttributeError): await devices[address].async_status() - await devices.async_load(id_devices=1) + load_aldb = 2 if devices.modem.aldb.read_write_mode == ReadWriteMode.UNKNOWN else 1 + await devices.async_load(id_devices=1, load_modem_aldb=load_aldb) for addr in devices: device = devices[addr] flags = True @@ -74,13 +78,19 @@ async def close_insteon_connection(*args): async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Insteon platform.""" + hass.data[DOMAIN] = {} if DOMAIN not in config: return True - conf = config[DOMAIN] + conf = dict(config[DOMAIN]) + hass.data[DOMAIN][CONF_DEV_PATH] = conf.pop(CONF_DEV_PATH, None) + + if not conf: + return True + data, options = convert_yaml_to_config_flow(conf) + if options: - hass.data[DOMAIN] = {} hass.data[DOMAIN][OPTIONS] = options # Create a config entry with the connection data hass.async_create_task( @@ -154,23 +164,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: platforms = get_device_platforms(device) if ON_OFF_EVENTS in platforms: add_on_off_event_device(hass, device) + create_insteon_device(hass, device, entry.entry_id) _LOGGER.debug("Insteon device count: %s", len(devices)) register_new_device_callback(hass) async_register_services(hass) - device_registry = await hass.helpers.device_registry.async_get_registry() - device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, str(devices.modem.address))}, - manufacturer="Smart Home", - name=f"{devices.modem.description} {devices.modem.address}", - model=f"{devices.modem.model} ({devices.modem.cat!r}, 0x{devices.modem.subcat:02x})", - sw_version=f"{devices.modem.firmware:02x} Engine Version: {devices.modem.engine_version}", - ) + create_insteon_device(hass, devices.modem, entry.entry_id) api.async_load_api(hass) + await api.async_register_insteon_frontend(hass) asyncio.create_task(async_get_device_config(hass, entry)) return True + + +def create_insteon_device(hass, device, config_entry_id): + """Create an Insteon device.""" + device_registry = dr.async_get(hass) + device_registry.async_get_or_create( + config_entry_id=config_entry_id, # entry.entry_id, + identifiers={(DOMAIN, str(device.address))}, + manufacturer="SmartLabs, Inc", + name=f"{device.description} {device.address}", + model=f"{device.model} ({device.cat!r}, 0x{device.subcat:02x})", + sw_version=f"{device.firmware:02x} Engine Version: {device.engine_version}", + ) diff --git a/homeassistant/components/insteon/api/__init__.py b/homeassistant/components/insteon/api/__init__.py index 3b786a38343..71dd1a0463e 100644 --- a/homeassistant/components/insteon/api/__init__.py +++ b/homeassistant/components/insteon/api/__init__.py @@ -1,7 +1,10 @@ """Insteon API interface for the frontend.""" -from homeassistant.components import websocket_api -from homeassistant.core import callback +from insteon_frontend import get_build_id, locate_dir + +from homeassistant.components import panel_custom, websocket_api +from homeassistant.components.insteon.const import CONF_DEV_PATH, DOMAIN +from homeassistant.core import HomeAssistant, callback from .aldb import ( websocket_add_default_links, @@ -13,7 +16,11 @@ from .aldb import ( websocket_reset_aldb, websocket_write_aldb, ) -from .device import websocket_get_device +from .device import ( + websocket_add_device, + websocket_cancel_add_device, + websocket_get_device, +) from .properties import ( websocket_change_properties_record, websocket_get_properties, @@ -22,11 +29,15 @@ from .properties import ( websocket_write_properties, ) +URL_BASE = "/insteon_static" + @callback def async_load_api(hass): """Set up the web socket API.""" websocket_api.async_register_command(hass, websocket_get_device) + websocket_api.async_register_command(hass, websocket_add_device) + websocket_api.async_register_command(hass, websocket_cancel_add_device) websocket_api.async_register_command(hass, websocket_get_aldb) websocket_api.async_register_command(hass, websocket_change_aldb_record) @@ -42,3 +53,31 @@ def async_load_api(hass): websocket_api.async_register_command(hass, websocket_write_properties) websocket_api.async_register_command(hass, websocket_load_properties) websocket_api.async_register_command(hass, websocket_reset_properties) + + +def get_entrypoint(is_dev): + """Get the entry point for the frontend.""" + if is_dev: + return "entrypoint.js" + + +async def async_register_insteon_frontend(hass: HomeAssistant): + """Register the Insteon frontend configuration panel.""" + # Add to sidepanel if needed + if DOMAIN not in hass.data.get("frontend_panels", {}): + dev_path = hass.data.get(DOMAIN, {}).get(CONF_DEV_PATH) + is_dev = dev_path is not None + path = dev_path if dev_path else locate_dir() + build_id = get_build_id(is_dev) + hass.http.register_static_path(URL_BASE, path, cache_headers=not is_dev) + + await panel_custom.async_register_panel( + hass=hass, + frontend_url_path=DOMAIN, + webcomponent_name="insteon-frontend", + sidebar_title=DOMAIN.capitalize(), + sidebar_icon="mdi:power", + module_url=f"{URL_BASE}/entrypoint-{build_id}.js", + embed_iframe=True, + require_admin=True, + ) diff --git a/homeassistant/components/insteon/api/device.py b/homeassistant/components/insteon/api/device.py index 1071451876b..beef78394fa 100644 --- a/homeassistant/components/insteon/api/device.py +++ b/homeassistant/components/insteon/api/device.py @@ -1,17 +1,20 @@ """API interface to get an Insteon device.""" from pyinsteon import devices +from pyinsteon.constants import DeviceAction import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from ..const import ( + DEVICE_ADDRESS, DEVICE_ID, DOMAIN, HA_DEVICE_NOT_FOUND, ID, INSTEON_DEVICE_NOT_FOUND, + MULTIPLE, TYPE, ) @@ -21,6 +24,12 @@ def compute_device_name(ha_device): return ha_device.name_by_user if ha_device.name_by_user else ha_device.name +async def async_add_devices(address, multiple): + """Add one or more Insteon devices.""" + async for _ in devices.async_add_device(address=address, multiple=multiple): + pass + + def get_insteon_device_from_ha_device(ha_device): """Return the Insteon device from an HA device.""" for identifier in ha_device.identifiers: @@ -74,3 +83,58 @@ async def websocket_get_device( "aldb_status": str(device.aldb.status), } connection.send_result(msg[ID], device_info) + + +@websocket_api.websocket_command( + { + vol.Required(TYPE): "insteon/device/add", + vol.Required(MULTIPLE): bool, + vol.Optional(DEVICE_ADDRESS): str, + } +) +@websocket_api.require_admin +@websocket_api.async_response +async def websocket_add_device( + hass: HomeAssistant, + connection: websocket_api.connection.ActiveConnection, + msg: dict, +) -> None: + """Add one or more Insteon devices.""" + + @callback + def linking_complete(address: str, action: DeviceAction): + """Forward device events to websocket.""" + if action == DeviceAction.COMPLETED: + forward_data = {"type": "linking_stopped", "address": ""} + else: + return + connection.send_message(websocket_api.event_message(msg["id"], forward_data)) + + @callback + def async_cleanup() -> None: + """Remove signal listeners.""" + devices.unsubscribe(linking_complete) + + connection.subscriptions[msg["id"]] = async_cleanup + devices.subscribe(linking_complete) + + async for address in devices.async_add_device( + address=msg.get(DEVICE_ADDRESS), multiple=msg[MULTIPLE] + ): + forward_data = {"type": "device_added", "address": str(address)} + connection.send_message(websocket_api.event_message(msg["id"], forward_data)) + + connection.send_result(msg[ID]) + + +@websocket_api.websocket_command({vol.Required(TYPE): "insteon/device/add/cancel"}) +@websocket_api.require_admin +@websocket_api.async_response +async def websocket_cancel_add_device( + hass: HomeAssistant, + connection: websocket_api.connection.ActiveConnection, + msg: dict, +) -> None: + """Cancel the Insteon all-linking process.""" + await devices.async_cancel_all_linking() + connection.send_result(msg[ID]) diff --git a/homeassistant/components/insteon/api/properties.py b/homeassistant/components/insteon/api/properties.py index 6ec12a5fd89..47def71c1ab 100644 --- a/homeassistant/components/insteon/api/properties.py +++ b/homeassistant/components/insteon/api/properties.py @@ -1,17 +1,15 @@ """Property update methods and schemas.""" -from itertools import chain from pyinsteon import devices -from pyinsteon.constants import RAMP_RATES, ResponseStatus -from pyinsteon.device_types.device_base import Device -from pyinsteon.extended_property import ( - NON_TOGGLE_MASK, - NON_TOGGLE_ON_OFF_MASK, - OFF_MASK, - ON_MASK, - RAMP_RATE, +from pyinsteon.config import RADIO_BUTTON_GROUPS, RAMP_RATE_IN_SEC, get_usable_value +from pyinsteon.constants import ( + RAMP_RATES_SEC, + PropertyType, + RelayMode, + ResponseStatus, + ToggleMode, ) -from pyinsteon.utils import ramp_rate_to_seconds, seconds_to_ramp_rate +from pyinsteon.device_types.device_base import Device import voluptuous as vol import voluptuous_serialize @@ -29,19 +27,12 @@ from ..const import ( ) from .device import notify_device_not_found -TOGGLE_ON_OFF_MODE = "toggle_on_off_mode" -NON_TOGGLE_ON_MODE = "non_toggle_on_mode" -NON_TOGGLE_OFF_MODE = "non_toggle_off_mode" -RADIO_BUTTON_GROUP_PROP = "radio_button_group_" -TOGGLE_PROP = "toggle_" -RAMP_RATE_SECONDS = list(dict.fromkeys(RAMP_RATES.values())) +SHOW_ADVANCED = "show_advanced" +RAMP_RATE_SECONDS = list(dict.fromkeys(RAMP_RATES_SEC)) RAMP_RATE_SECONDS.sort() -TOGGLE_MODES = {TOGGLE_ON_OFF_MODE: 0, NON_TOGGLE_ON_MODE: 1, NON_TOGGLE_OFF_MODE: 2} -TOGGLE_MODES_SCHEMA = { - 0: TOGGLE_ON_OFF_MODE, - 1: NON_TOGGLE_ON_MODE, - 2: NON_TOGGLE_OFF_MODE, -} +RAMP_RATE_LIST = [str(seconds) for seconds in RAMP_RATE_SECONDS] +TOGGLE_MODES = [str(ToggleMode(v)).lower() for v in list(ToggleMode)] +RELAY_MODES = [str(RelayMode(v)).lower() for v in list(RelayMode)] def _bool_schema(name): @@ -52,239 +43,116 @@ def _byte_schema(name): return voluptuous_serialize.convert(vol.Schema({vol.Required(name): cv.byte}))[0] -def _ramp_rate_schema(name): +def _float_schema(name): + return voluptuous_serialize.convert(vol.Schema({vol.Required(name): float}))[0] + + +def _list_schema(name, values): return voluptuous_serialize.convert( - vol.Schema({vol.Required(name): vol.In(RAMP_RATE_SECONDS)}), + vol.Schema({vol.Required(name): vol.In(values)}), custom_serializer=cv.custom_serializer, )[0] -def get_properties(device: Device): +def _multi_select_schema(name, values): + return voluptuous_serialize.convert( + vol.Schema({vol.Optional(name): cv.multi_select(values)}), + custom_serializer=cv.custom_serializer, + )[0] + + +def _read_only_schema(name, value): + """Return a constant value schema.""" + return voluptuous_serialize.convert(vol.Schema({vol.Required(name): value}))[0] + + +def get_schema(prop, name, groups): + """Return the correct shema type.""" + if prop.is_read_only: + return _read_only_schema(name, prop.value) + if name == RAMP_RATE_IN_SEC: + return _list_schema(name, RAMP_RATE_LIST) + if name == RADIO_BUTTON_GROUPS: + button_list = {str(group): groups[group].name for group in groups if group != 1} + return _multi_select_schema(name, button_list) + if prop.value_type == bool: + return _bool_schema(name) + if prop.value_type == int: + return _byte_schema(name) + if prop.value_type == float: + return _float_schema(name) + if prop.value_type == ToggleMode: + return _list_schema(name, TOGGLE_MODES) + if prop.value_type == RelayMode: + return _list_schema(name, RELAY_MODES) + return None + + +def get_properties(device: Device, show_advanced=False): """Get the properties of an Insteon device and return the records and schema.""" properties = [] schema = {} - # Limit the properties we manage at this time. - for prop_name in device.operating_flags: - if not device.operating_flags[prop_name].is_read_only: - prop_dict, schema_dict = _get_property(device.operating_flags[prop_name]) - properties.append(prop_dict) - schema[prop_name] = schema_dict - - mask_found = False - for prop_name in device.properties: - if device.properties[prop_name].is_read_only: + for name, prop in device.configuration.items(): + if prop.is_read_only and not show_advanced: continue - if prop_name == RAMP_RATE: - rr_prop, rr_schema = _get_ramp_rate_property(device.properties[prop_name]) - properties.append(rr_prop) - schema[RAMP_RATE] = rr_schema + prop_schema = get_schema(prop, name, device.groups) + if name == "momentary_delay": + print(prop_schema) + if prop_schema is None: + continue + schema[name] = prop_schema + properties.append(property_to_dict(prop)) - elif not mask_found and "mask" in prop_name: - mask_found = True - toggle_props, toggle_schema = _get_toggle_properties(device) - properties.extend(toggle_props) - schema.update(toggle_schema) - - rb_props, rb_schema = _get_radio_button_properties(device) - properties.extend(rb_props) - schema.update(rb_schema) - else: - prop_dict, schema_dict = _get_property(device.properties[prop_name]) - properties.append(prop_dict) - schema[prop_name] = schema_dict + if show_advanced: + for name, prop in device.operating_flags.items(): + if prop.property_type != PropertyType.ADVANCED: + continue + prop_schema = get_schema(prop, name, device.groups) + if prop_schema is not None: + schema[name] = prop_schema + properties.append(property_to_dict(prop)) + for name, prop in device.properties.items(): + if prop.property_type != PropertyType.ADVANCED: + continue + prop_schema = get_schema(prop, name, device.groups) + if prop_schema is not None: + schema[name] = prop_schema + properties.append(property_to_dict(prop)) return properties, schema -def set_property(device, prop_name: str, value): - """Update a property value.""" - if isinstance(value, bool) and prop_name in device.operating_flags: - device.operating_flags[prop_name].new_value = value - - elif prop_name == RAMP_RATE: - device.properties[prop_name].new_value = seconds_to_ramp_rate(value) - - elif prop_name.startswith(RADIO_BUTTON_GROUP_PROP): - buttons = [int(button) for button in value] - rb_groups = _calc_radio_button_groups(device) - curr_group = int(prop_name[len(RADIO_BUTTON_GROUP_PROP) :]) - if len(rb_groups) > curr_group: - removed = [btn for btn in rb_groups[curr_group] if btn not in buttons] - if removed: - device.clear_radio_buttons(removed) - if buttons: - device.set_radio_buttons(buttons) - - elif prop_name.startswith(TOGGLE_PROP): - button_name = prop_name[len(TOGGLE_PROP) :] - for button in device.groups: - if device.groups[button].name == button_name: - device.set_toggle_mode(button, int(value)) - - else: - device.properties[prop_name].new_value = value - - -def _get_property(prop): +def property_to_dict(prop): """Return a property data row.""" - value, modified = _get_usable_value(prop) + value = get_usable_value(prop) + modified = value == prop.new_value + if prop.value_type in [ToggleMode, RelayMode] or prop.name == RAMP_RATE_IN_SEC: + value = str(value).lower() prop_dict = {"name": prop.name, "value": value, "modified": modified} - if isinstance(prop.value, bool): - schema = _bool_schema(prop.name) + return prop_dict + + +def update_property(device, prop_name, value): + """Update the value of a device property.""" + prop = device.configuration[prop_name] + if prop.value_type == ToggleMode: + toggle_mode = getattr(ToggleMode, value.upper()) + prop.new_value = toggle_mode + elif prop.value_type == RelayMode: + relay_mode = getattr(RelayMode, value.upper()) + prop.new_value = relay_mode else: - schema = _byte_schema(prop.name) - return prop_dict, {"name": prop.name, **schema} - - -def _get_toggle_properties(device): - """Generate the mask properties for a KPL device.""" - props = [] - schema = {} - toggle_prop = device.properties[NON_TOGGLE_MASK] - toggle_on_prop = device.properties[NON_TOGGLE_ON_OFF_MASK] - for button in device.groups: - name = f"{TOGGLE_PROP}{device.groups[button].name}" - value, modified = _toggle_button_value(toggle_prop, toggle_on_prop, button) - props.append({"name": name, "value": value, "modified": modified}) - toggle_schema = vol.Schema({vol.Required(name): vol.In(TOGGLE_MODES_SCHEMA)}) - toggle_schema_dict = voluptuous_serialize.convert( - toggle_schema, custom_serializer=cv.custom_serializer - ) - schema[name] = toggle_schema_dict[0] - return props, schema - - -def _toggle_button_value(non_toggle_prop, toggle_on_prop, button): - """Determine the toggle value of a button.""" - toggle_mask, toggle_modified = _get_usable_value(non_toggle_prop) - toggle_on_mask, toggle_on_modified = _get_usable_value(toggle_on_prop) - - bit = button - 1 - if not toggle_mask & 1 << bit: - value = 0 - else: - if toggle_on_mask & 1 << bit: - value = 1 - else: - value = 2 - - modified = False - if toggle_modified: - curr_bit = non_toggle_prop.value & 1 << bit - new_bit = non_toggle_prop.new_value & 1 << bit - modified = not curr_bit == new_bit - - if not modified and value != 0 and toggle_on_modified: - curr_bit = toggle_on_prop.value & 1 << bit - new_bit = toggle_on_prop.new_value & 1 << bit - modified = not curr_bit == new_bit - - return value, modified - - -def _get_radio_button_properties(device): - """Return the values and schema to set KPL buttons as radio buttons.""" - rb_groups = _calc_radio_button_groups(device) - props = [] - schema = {} - index = 0 - remaining_buttons = [] - - buttons_in_groups = list(chain.from_iterable(rb_groups)) - - # Identify buttons not belonging to any group - for button in device.groups: - if button not in buttons_in_groups: - remaining_buttons.append(button) - - for rb_group in rb_groups: - name = f"{RADIO_BUTTON_GROUP_PROP}{index}" - button_1 = rb_group[0] - button_str = f"_{button_1}" if button_1 != 1 else "" - on_mask = device.properties[f"{ON_MASK}{button_str}"] - off_mask = device.properties[f"{OFF_MASK}{button_str}"] - modified = on_mask.is_dirty or off_mask.is_dirty - - props.append( - { - "name": name, - "modified": modified, - "value": rb_group, - } - ) - - options = { - button: device.groups[button].name - for button in chain.from_iterable([rb_group, remaining_buttons]) - } - rb_schema = vol.Schema({vol.Optional(name): cv.multi_select(options)}) - - rb_schema_dict = voluptuous_serialize.convert( - rb_schema, custom_serializer=cv.custom_serializer - ) - schema[name] = rb_schema_dict[0] - - index += 1 - - if len(remaining_buttons) > 1: - name = f"{RADIO_BUTTON_GROUP_PROP}{index}" - - props.append( - { - "name": name, - "modified": False, - "value": [], - } - ) - - options = {button: device.groups[button].name for button in remaining_buttons} - rb_schema = vol.Schema({vol.Optional(name): cv.multi_select(options)}) - - rb_schema_dict = voluptuous_serialize.convert( - rb_schema, custom_serializer=cv.custom_serializer - ) - schema[name] = rb_schema_dict[0] - - return props, schema - - -def _calc_radio_button_groups(device): - """Return existing radio button groups.""" - rb_groups = [] - for button in device.groups: - if button not in list(chain.from_iterable(rb_groups)): - button_str = "" if button == 1 else f"_{button}" - on_mask, _ = _get_usable_value(device.properties[f"{ON_MASK}{button_str}"]) - if on_mask != 0: - rb_group = [button] - for bit in list(range(0, button - 1)) + list(range(button, 8)): - if on_mask & 1 << bit: - rb_group.append(bit + 1) - if len(rb_group) > 1: - rb_groups.append(rb_group) - return rb_groups - - -def _get_ramp_rate_property(prop): - """Return the value and schema of a ramp rate property.""" - rr_prop, _ = _get_property(prop) - rr_prop["value"] = ramp_rate_to_seconds(rr_prop["value"]) - return rr_prop, _ramp_rate_schema(prop.name) - - -def _get_usable_value(prop): - """Return the current or the modified value of a property.""" - value = prop.value if prop.new_value is None else prop.new_value - return value, prop.is_dirty + prop.new_value = value @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/properties/get", vol.Required(DEVICE_ADDRESS): str, + vol.Required(SHOW_ADVANCED): bool, } ) @websocket_api.require_admin @@ -299,7 +167,7 @@ async def websocket_get_properties( notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND) return - properties, schema = get_properties(device) + properties, schema = get_properties(device, msg[SHOW_ADVANCED]) connection.send_result(msg[ID], {"properties": properties, "schema": schema}) @@ -324,7 +192,7 @@ async def websocket_change_properties_record( notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND) return - set_property(device, msg[PROPERTY_NAME], msg[PROPERTY_VALUE]) + update_property(device, msg[PROPERTY_NAME], msg[PROPERTY_VALUE]) connection.send_result(msg[ID]) @@ -346,10 +214,9 @@ async def websocket_write_properties( notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND) return - result1 = await device.async_write_op_flags() - result2 = await device.async_write_ext_properties() + result = await device.async_write_config() await devices.async_save(workdir=hass.config.config_dir) - if result1 != ResponseStatus.SUCCESS or result2 != ResponseStatus.SUCCESS: + if result != ResponseStatus.SUCCESS: connection.send_message( websocket_api.error_message( msg[ID], "write_failed", "properties not written to device" @@ -377,10 +244,9 @@ async def websocket_load_properties( notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND) return - result1 = await device.async_read_op_flags() - result2 = await device.async_read_ext_properties() + result, _ = await device.async_read_config(read_aldb=False) await devices.async_save(workdir=hass.config.config_dir) - if result1 != ResponseStatus.SUCCESS or result2 != ResponseStatus.SUCCESS: + if result != ResponseStatus.SUCCESS: connection.send_message( websocket_api.error_message( msg[ID], "load_failed", "properties not loaded from device" diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 7799560bdfd..833180583e2 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -1,28 +1,19 @@ """Support for Insteon thermostat.""" from __future__ import annotations +from pyinsteon.config import CELSIUS from pyinsteon.constants import ThermostatMode -from pyinsteon.operating_flag import CELSIUS from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, DOMAIN as CLIMATE_DOMAIN, HVAC_MODE_AUTO, - HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -50,18 +41,12 @@ HUMIDITY_LOW = 17 HVAC_MODES = { - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, - 3: HVAC_MODE_HEAT_COOL, + 0: HVACMode.OFF, + 1: HVACMode.HEAT, + 2: HVACMode.COOL, + 3: HVACMode.HEAT_COOL, } FAN_MODES = {4: HVAC_MODE_AUTO, 8: HVAC_MODE_FAN_ONLY} -SUPPORTED_FEATURES = ( - SUPPORT_FAN_MODE - | SUPPORT_TARGET_HUMIDITY - | SUPPORT_TARGET_TEMPERATURE - | SUPPORT_TARGET_TEMPERATURE_RANGE -) async def async_setup_entry( @@ -90,10 +75,12 @@ async def async_setup_entry( class InsteonClimateEntity(InsteonEntity, ClimateEntity): """A Class for an Insteon climate entity.""" - @property - def supported_features(self): - """Return the supported features for this entity.""" - return SUPPORTED_FEATURES + _attr_supported_features = ( + ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.TARGET_HUMIDITY + | ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) @property def temperature_unit(self) -> str: @@ -108,12 +95,12 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return self._insteon_device.groups[HUMIDITY].value @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" return HVAC_MODES[self._insteon_device.groups[SYSTEM_MODE].value] @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" return list(HVAC_MODES.values()) @@ -169,18 +156,18 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): return 1 @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. """ if self._insteon_device.groups[COOLING].value: - return CURRENT_HVAC_COOL + return HVACAction.COOLING if self._insteon_device.groups[HEATING].value: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if self._insteon_device.groups[FAN_MODE].value == ThermostatMode.FAN_ALWAYS_ON: - return CURRENT_HVAC_FAN - return CURRENT_HVAC_IDLE + return HVACAction.FAN + return HVACAction.IDLE @property def extra_state_attributes(self): @@ -213,7 +200,7 @@ class InsteonClimateEntity(InsteonEntity, ClimateEntity): mode = list(FAN_MODES)[list(FAN_MODES.values()).index(fan_mode)] await self._insteon_device.async_set_mode(mode) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" mode = list(HVAC_MODES)[list(HVAC_MODES.values()).index(hvac_mode)] await self._insteon_device.async_set_mode(mode) diff --git a/homeassistant/components/insteon/config_flow.py b/homeassistant/components/insteon/config_flow.py index 5bf8769f321..be68a66b70a 100644 --- a/homeassistant/components/insteon/config_flow.py +++ b/homeassistant/components/insteon/config_flow.py @@ -7,7 +7,7 @@ from pyinsteon import async_connect import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import usb +from homeassistant.components import dhcp, usb from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE, @@ -19,6 +19,7 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( @@ -114,6 +115,7 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): _device_path: str | None = None _device_name: str | None = None + discovered_conf: dict[str, str] = {} @staticmethod @callback @@ -170,7 +172,7 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title="", data=user_input) user_input.pop(CONF_HUB_VERSION) errors["base"] = "cannot_connect" - schema_defaults = user_input if user_input is not None else {} + schema_defaults = user_input if user_input is not None else self.discovered_conf data_schema = build_hub_schema(hub_version=hub_version, **schema_defaults) step_id = STEP_HUB_V2 if hub_version == 2 else STEP_HUB_V1 return self.async_show_form( @@ -203,12 +205,14 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): discovery_info.pid, ) self._set_confirm_only() - self.context["title_placeholders"] = {CONF_NAME: self._device_name} + self.context["title_placeholders"] = { + CONF_NAME: f"Insteon PLM {self._device_name}" + } await self.async_set_unique_id(config_entries.DEFAULT_DISCOVERY_UNIQUE_ID) return await self.async_step_confirm_usb() async def async_step_confirm_usb(self, user_input=None): - """Confirm a discovery.""" + """Confirm a USB discovery.""" if user_input is not None: return await self.async_step_plm({CONF_DEVICE: self._device_path}) @@ -217,6 +221,15 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={CONF_NAME: self._device_name}, ) + async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + """Handle a DHCP discovery.""" + self.discovered_conf = {CONF_HOST: discovery_info.ip} + self.context["title_placeholders"] = { + CONF_NAME: f"Insteon Hub {discovery_info.ip}" + } + await self.async_set_unique_id(format_mac(discovery_info.macaddress)) + return await self.async_step_user() + class InsteonOptionsFlowHandler(config_entries.OptionsFlow): """Handle an Insteon options flow.""" diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index bc3eaf6234b..fb7b2387d73 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -70,6 +70,7 @@ CONF_DIM_STEPS = "dim_steps" CONF_X10_ALL_UNITS_OFF = "x10_all_units_off" CONF_X10_ALL_LIGHTS_ON = "x10_all_lights_on" CONF_X10_ALL_LIGHTS_OFF = "x10_all_lights_off" +CONF_DEV_PATH = "dev_path" PORT_HUB_V1 = 9761 PORT_HUB_V2 = 25105 @@ -172,5 +173,6 @@ PROPERTY_NAME = "name" PROPERTY_VALUE = "value" HA_DEVICE_NOT_FOUND = "ha_device_not_found" INSTEON_DEVICE_NOT_FOUND = "insteon_device_not_found" +MULTIPLE = "multiple" INSTEON_ADDR_REGEX = re.compile(r"([A-Fa-f0-9]{2}\.?[A-Fa-f0-9]{2}\.?[A-Fa-f0-9]{2})$") diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index f66b1329bc6..defa1acaa38 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -4,10 +4,8 @@ import math from homeassistant.components.cover import ( ATTR_POSITION, DOMAIN as COVER_DOMAIN, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -18,8 +16,6 @@ from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - async def async_setup_entry( hass: HomeAssistant, @@ -43,6 +39,12 @@ async def async_setup_entry( class InsteonCoverEntity(InsteonEntity, CoverEntity): """A Class for an Insteon cover entity.""" + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + @property def current_cover_position(self): """Return the current cover position.""" @@ -52,11 +54,6 @@ class InsteonCoverEntity(InsteonEntity, CoverEntity): pos = 0 return int(math.ceil(pos * 100 / 255)) - @property - def supported_features(self): - """Return the supported features for this entity.""" - return SUPPORTED_FEATURES - @property def is_closed(self): """Return the boolean response if the node is on.""" diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index bea17ccaa7e..8639dfb79fe 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -5,8 +5,8 @@ import math from homeassistant.components.fan import ( DOMAIN as FAN_DOMAIN, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -46,6 +46,8 @@ async def async_setup_entry( class InsteonFanEntity(InsteonEntity, FanEntity): """An INSTEON fan entity.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + @property def percentage(self) -> int | None: """Return the current speed percentage.""" @@ -53,11 +55,6 @@ class InsteonFanEntity(InsteonEntity, FanEntity): return None return ranged_value_to_percentage(SPEED_RANGE, self._insteon_device_group.value) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED - @property def speed_count(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index d8fd9b2cbc9..60935f3f951 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -85,7 +85,7 @@ class InsteonEntity(Entity): """Return device information.""" return DeviceInfo( identifiers={(DOMAIN, str(self._insteon_device.address))}, - manufacturer="Smart Home", + manufacturer="SmartLabs, Inc", model=f"{self._insteon_device.model} ({self._insteon_device.cat!r}, 0x{self._insteon_device.subcat:02x})", name=f"{self._insteon_device.description} {self._insteon_device.address}", sw_version=f"{self._insteon_device.firmware:02x} Engine Version: {self._insteon_device.engine_version}", diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index 921432d282f..05ad9794042 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -1,10 +1,10 @@ """Support for Insteon lights via PowerLinc Modem.""" -from pyinsteon.extended_property import ON_LEVEL +from pyinsteon.config import ON_LEVEL from homeassistant.components.light import ( ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -41,6 +41,9 @@ async def async_setup_entry( class InsteonDimmerEntity(InsteonEntity, LightEntity): """A Class for an Insteon light entity.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + @property def brightness(self): """Return the brightness of this light between 0..255.""" @@ -51,11 +54,6 @@ class InsteonDimmerEntity(InsteonEntity, LightEntity): """Return the boolean response if the node is on.""" return bool(self.brightness) - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - async def async_turn_on(self, **kwargs): """Turn light on.""" if ATTR_BRIGHTNESS in kwargs: diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 63eb24ee453..c69f4f2cdf5 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,12 +2,24 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.13"], + "dependencies": ["http", "websocket_api"], + "requirements": [ + "pyinsteon==1.1.0", + "insteon-frontend-home-assistant==0.1.0" + ], "codeowners": ["@teharris1"], + "dhcp": [ + { + "macaddress": "000EF3*" + }, + { + "registered_devices": true + } + ], "config_flow": true, "iot_class": "local_push", "loggers": ["pyinsteon", "pypubsub"], - "after_dependencies": ["usb"], + "after_dependencies": ["panel_custom", "usb"], "usb": [ { "vid": "10BF" diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 09315919052..6bcde545e34 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -22,6 +22,7 @@ import homeassistant.helpers.config_validation as cv from .const import ( CONF_CAT, + CONF_DEV_PATH, CONF_DIM_STEPS, CONF_FIRMWARE, CONF_HOUSECODE, @@ -121,6 +122,7 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_X10): vol.All( cv.ensure_list_csv, [CONF_X10_SCHEMA] ), + vol.Optional(CONF_DEV_PATH): cv.string, }, extra=vol.ALLOW_EXTRA, required=True, diff --git a/homeassistant/components/insteon/strings.json b/homeassistant/components/insteon/strings.json index 793a38a2694..e451e080539 100644 --- a/homeassistant/components/insteon/strings.json +++ b/homeassistant/components/insteon/strings.json @@ -43,7 +43,8 @@ }, "abort": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "not_insteon_device": "Discovered device not an Insteon device" } }, "options": { diff --git a/homeassistant/components/insteon/translations/ca.json b/homeassistant/components/insteon/translations/ca.json index 59c711c3dae..9805d03c685 100644 --- a/homeassistant/components/insteon/translations/ca.json +++ b/homeassistant/components/insteon/translations/ca.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Ha fallat la connexi\u00f3", + "not_insteon_device": "El dispositiu descobert no \u00e9s un dispositiu Insteon", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "select_single": "Selecciona una opci\u00f3." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Vols configurar {name}?" + }, "hubv1": { "data": { "host": "Adre\u00e7a IP", diff --git a/homeassistant/components/insteon/translations/de.json b/homeassistant/components/insteon/translations/de.json index 0866f53481f..164fbccceed 100644 --- a/homeassistant/components/insteon/translations/de.json +++ b/homeassistant/components/insteon/translations/de.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Verbindung fehlgeschlagen", + "not_insteon_device": "Erkanntes Ger\u00e4t ist kein Insteon-Ger\u00e4t", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "select_single": "W\u00e4hle eine Option aus." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "M\u00f6chtest du {name} einrichten?" + }, "hubv1": { "data": { "host": "IP-Adresse", diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json index f26c9cba54c..4a4c402ddc0 100644 --- a/homeassistant/components/insteon/translations/el.json +++ b/homeassistant/components/insteon/translations/el.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "not_insteon_device": "\u0397 \u03b1\u03bd\u03b1\u03ba\u03b1\u03bb\u03c5\u03c6\u03b8\u03b5\u03af\u03c3\u03b1 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae Insteon", "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "select_single": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name};" + }, "hubv1": { "data": { "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", diff --git a/homeassistant/components/insteon/translations/en.json b/homeassistant/components/insteon/translations/en.json index 4c4a439b938..f9e64dcf3cf 100644 --- a/homeassistant/components/insteon/translations/en.json +++ b/homeassistant/components/insteon/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "cannot_connect": "Failed to connect", + "not_insteon_device": "Discovered device not an Insteon device", "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { diff --git a/homeassistant/components/insteon/translations/et.json b/homeassistant/components/insteon/translations/et.json index 69368300c7e..eff2fd9b9b2 100644 --- a/homeassistant/components/insteon/translations/et.json +++ b/homeassistant/components/insteon/translations/et.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "\u00dchendamine nurjus", + "not_insteon_device": "Avastatud seade ei ole Insteoni seade", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." }, "error": { "cannot_connect": "\u00dchendamine nurjus", "select_single": "Vali \u00fcks suvand." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Kas seadistada {name} ?" + }, "hubv1": { "data": { "host": "IP aagress", diff --git a/homeassistant/components/insteon/translations/fr.json b/homeassistant/components/insteon/translations/fr.json index 2feb3c5d9c9..2bb0e4d2e33 100644 --- a/homeassistant/components/insteon/translations/fr.json +++ b/homeassistant/components/insteon/translations/fr.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "\u00c9chec de connexion", + "not_insteon_device": "L'appareil d\u00e9couvert n'est pas un appareil Insteon", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "error": { "cannot_connect": "\u00c9chec de connexion", "select_single": "S\u00e9lectionnez une option." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Voulez-vous configurer {name}\u00a0?" + }, "hubv1": { "data": { "host": "Adresse IP", diff --git a/homeassistant/components/insteon/translations/he.json b/homeassistant/components/insteon/translations/he.json index 220bbcbb632..68f13ca5a01 100644 --- a/homeassistant/components/insteon/translations/he.json +++ b/homeassistant/components/insteon/translations/he.json @@ -7,6 +7,7 @@ "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, + "flow_title": "{name}", "step": { "hubv1": { "data": { diff --git a/homeassistant/components/insteon/translations/hu.json b/homeassistant/components/insteon/translations/hu.json index f34307a67a4..560c1fc118e 100644 --- a/homeassistant/components/insteon/translations/hu.json +++ b/homeassistant/components/insteon/translations/hu.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "not_insteon_device": "A felfedezett eszk\u00f6z nem egy Insteon", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "select_single": "V\u00e1lasszon egy lehet\u0151s\u00e9get" }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {name}?" + }, "hubv1": { "data": { "host": "IP c\u00edm", diff --git a/homeassistant/components/insteon/translations/id.json b/homeassistant/components/insteon/translations/id.json index efae5284150..aae7e9f71ac 100644 --- a/homeassistant/components/insteon/translations/id.json +++ b/homeassistant/components/insteon/translations/id.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Gagal terhubung", + "not_insteon_device": "Perangkat yang ditemukan bukan perangkat Insteon", "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "error": { "cannot_connect": "Gagal terhubung", "select_single": "Pilih satu opsi." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Ingin menyiapkan {name}?" + }, "hubv1": { "data": { "host": "Alamat IP", diff --git a/homeassistant/components/insteon/translations/it.json b/homeassistant/components/insteon/translations/it.json index 83a4e87d6f6..b47a06c9d7a 100644 --- a/homeassistant/components/insteon/translations/it.json +++ b/homeassistant/components/insteon/translations/it.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Impossibile connettersi", + "not_insteon_device": "Dispositivo rilevato non \u00e8 un dispositivo Insteon", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { "cannot_connect": "Impossibile connettersi", "select_single": "Seleziona un'opzione." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Vuoi configurare {name}?" + }, "hubv1": { "data": { "host": "Indirizzo IP", @@ -46,7 +51,7 @@ "options": { "error": { "cannot_connect": "Impossibile connettersi", - "input_error": "Voci non valide, si prega di controllare i valori.", + "input_error": "Voci non valide, controlla i valori.", "select_single": "Seleziona un'opzione." }, "step": { diff --git a/homeassistant/components/insteon/translations/ja.json b/homeassistant/components/insteon/translations/ja.json index d4ddf083f1b..0b8d93518bd 100644 --- a/homeassistant/components/insteon/translations/ja.json +++ b/homeassistant/components/insteon/translations/ja.json @@ -8,7 +8,11 @@ "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "select_single": "\u30aa\u30d7\u30b7\u30e7\u30f3\u30921\u3064\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "{name} \u3092\u8a2d\u5b9a\u3057\u307e\u3059\u304b\uff1f" + }, "hubv1": { "data": { "host": "IP\u30a2\u30c9\u30ec\u30b9", @@ -61,7 +65,7 @@ }, "add_x10": { "data": { - "housecode": "\u30cf\u30a6\u30b9\u30b3\u30fc\u30c9(a\uff5ep)", + "housecode": "\u30cf\u30a6\u30b9\u30b3\u30fc\u30c9(a - p)", "platform": "\u30d7\u30e9\u30c3\u30c8\u30db\u30fc\u30e0", "steps": "\u8abf\u5149\u30b9\u30c6\u30c3\u30d7(\u30e9\u30a4\u30c8\u30c7\u30d0\u30a4\u30b9\u306e\u307f\u3001\u30c7\u30d5\u30a9\u30eb\u30c822)", "unitcode": "\u30e6\u30cb\u30c3\u30c8\u30b3\u30fc\u30c9(1\u301c16)" diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index 63a0bb059d5..207e370e245 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Kon niet verbinden", + "not_insteon_device": "Ontdekt apparaat is geen Insteon apparaat", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { "cannot_connect": "Kon niet verbinden", "select_single": "Selecteer een optie." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Wilt u {name} instellen?" + }, "hubv1": { "data": { "host": "IP-adres", diff --git a/homeassistant/components/insteon/translations/no.json b/homeassistant/components/insteon/translations/no.json index b5d6a2c3105..3716312b00f 100644 --- a/homeassistant/components/insteon/translations/no.json +++ b/homeassistant/components/insteon/translations/no.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Tilkobling mislyktes", + "not_insteon_device": "Oppdaget enhet, ikke en Insteon-enhet", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { "cannot_connect": "Tilkobling mislyktes", "select_single": "Velg ett alternativ." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Vil du konfigurere {name}?" + }, "hubv1": { "data": { "host": "IP adresse", diff --git a/homeassistant/components/insteon/translations/pl.json b/homeassistant/components/insteon/translations/pl.json index c4a58e0e09a..6d3b6c4391d 100644 --- a/homeassistant/components/insteon/translations/pl.json +++ b/homeassistant/components/insteon/translations/pl.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "not_insteon_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem Insteon", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "select_single": "Wybierz jedn\u0105 z opcji" }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Czy chcesz skonfigurowa\u0107 {name}?" + }, "hubv1": { "data": { "host": "Adres IP", diff --git a/homeassistant/components/insteon/translations/pt-BR.json b/homeassistant/components/insteon/translations/pt-BR.json index 409ac9d6283..b5487d9260a 100644 --- a/homeassistant/components/insteon/translations/pt-BR.json +++ b/homeassistant/components/insteon/translations/pt-BR.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Falha ao conectar", + "not_insteon_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo Insteon", "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { "cannot_connect": "Falha ao conectar", "select_single": "Selecione uma op\u00e7\u00e3o." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "Deseja configurar {name}?" + }, "hubv1": { "data": { "host": "Endere\u00e7o IP", diff --git a/homeassistant/components/insteon/translations/ru.json b/homeassistant/components/insteon/translations/ru.json index 69b5354fe87..3151fa35252 100644 --- a/homeassistant/components/insteon/translations/ru.json +++ b/homeassistant/components/insteon/translations/ru.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "not_insteon_device": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 Insteon.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "select_single": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u043f\u0446\u0438\u044e." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" + }, "hubv1": { "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", diff --git a/homeassistant/components/insteon/translations/tr.json b/homeassistant/components/insteon/translations/tr.json index 3cb23de55f7..2f74d6a98ae 100644 --- a/homeassistant/components/insteon/translations/tr.json +++ b/homeassistant/components/insteon/translations/tr.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_insteon_device": "Ke\u015ffedilen cihaz bir Insteon cihaz\u0131 de\u011fil", "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", "select_single": "Bir se\u00e7enek belirleyin." }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "{name} kurulumunu yapmak istiyor musunuz?" + }, "hubv1": { "data": { "host": "IP Adresi", diff --git a/homeassistant/components/insteon/translations/zh-Hant.json b/homeassistant/components/insteon/translations/zh-Hant.json index 2176ea67b94..c55a1ea0a5c 100644 --- a/homeassistant/components/insteon/translations/zh-Hant.json +++ b/homeassistant/components/insteon/translations/zh-Hant.json @@ -2,13 +2,18 @@ "config": { "abort": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "not_insteon_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e Insteon \u88dd\u7f6e", "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "select_single": "\u9078\u64c7\u9078\u9805\u3002" }, + "flow_title": "{name}", "step": { + "confirm_usb": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" + }, "hubv1": { "data": { "host": "IP \u4f4d\u5740", diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 1599975f462..03647559345 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -4,7 +4,7 @@ import logging from pyinsteon import devices from pyinsteon.address import Address -from pyinsteon.constants import ALDBStatus +from pyinsteon.constants import ALDBStatus, DeviceAction from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT from pyinsteon.managers.link_manager import ( async_enter_linking_mode, @@ -137,9 +137,10 @@ def register_new_device_callback(hass): """Register callback for new Insteon device.""" @callback - def async_new_insteon_device(address=None): + def async_new_insteon_device(address, action: DeviceAction): """Detect device from transport to be delegated to platform.""" - hass.async_create_task(async_create_new_entities(address)) + if action == DeviceAction.ADDED: + hass.async_create_task(async_create_new_entities(address)) async def async_create_new_entities(address): _LOGGER.debug( diff --git a/homeassistant/components/integration/config_flow.py b/homeassistant/components/integration/config_flow.py index 3f7d39554e8..aab5671f64b 100644 --- a/homeassistant/components/integration/config_flow.py +++ b/homeassistant/components/integration/config_flow.py @@ -9,7 +9,6 @@ import voluptuous as vol from homeassistant.const import ( CONF_METHOD, CONF_NAME, - CONF_UNIT_OF_MEASUREMENT, TIME_DAYS, TIME_HOURS, TIME_MINUTES, @@ -34,56 +33,58 @@ from .const import ( ) UNIT_PREFIXES = [ - {"value": "none", "label": "none"}, - {"value": "k", "label": "k (kilo)"}, - {"value": "M", "label": "M (mega)"}, - {"value": "G", "label": "G (giga)"}, - {"value": "T", "label": "T (tera)"}, + selector.SelectOptionDict(value="none", label="none"), + selector.SelectOptionDict(value="k", label="k (kilo)"), + selector.SelectOptionDict(value="M", label="M (mega)"), + selector.SelectOptionDict(value="G", label="G (giga)"), + selector.SelectOptionDict(value="T", label="T (tera)"), ] TIME_UNITS = [ - {"value": TIME_SECONDS, "label": "s (seconds)"}, - {"value": TIME_MINUTES, "label": "min (minutes)"}, - {"value": TIME_HOURS, "label": "h (hours)"}, - {"value": TIME_DAYS, "label": "d (days)"}, + selector.SelectOptionDict(value=TIME_SECONDS, label="s (seconds)"), + selector.SelectOptionDict(value=TIME_MINUTES, label="min (minutes)"), + selector.SelectOptionDict(value=TIME_HOURS, label="h (hours)"), + selector.SelectOptionDict(value=TIME_DAYS, label="d (days)"), ] INTEGRATION_METHODS = [ - {"value": METHOD_TRAPEZOIDAL, "label": "Trapezoidal rule"}, - {"value": METHOD_LEFT, "label": "Left Riemann sum"}, - {"value": METHOD_RIGHT, "label": "Right Riemann sum"}, + selector.SelectOptionDict(value=METHOD_TRAPEZOIDAL, label="Trapezoidal rule"), + selector.SelectOptionDict(value=METHOD_LEFT, label="Left Riemann sum"), + selector.SelectOptionDict(value=METHOD_RIGHT, label="Right Riemann sum"), ] OPTIONS_SCHEMA = vol.Schema( { - vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector( - {"number": {"min": 0, "max": 6, "mode": "box"}} + vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=6, mode=selector.NumberSelectorMode.BOX + ), ), } ) CONFIG_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): selector.selector({"text": {}}), - vol.Required(CONF_SOURCE_SENSOR): selector.selector( - {"entity": {"domain": "sensor"}}, + vol.Required(CONF_NAME): selector.TextSelector(), + vol.Required(CONF_SOURCE_SENSOR): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor") ), - vol.Required(CONF_METHOD, default=METHOD_TRAPEZOIDAL): selector.selector( - {"select": {"options": INTEGRATION_METHODS}} + vol.Required(CONF_METHOD, default=METHOD_TRAPEZOIDAL): selector.SelectSelector( + selector.SelectSelectorConfig(options=INTEGRATION_METHODS), ), - vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector( - { - "number": { - "min": 0, - "max": 6, - "mode": "box", - CONF_UNIT_OF_MEASUREMENT: "decimals", - } - } + vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=6, + mode=selector.NumberSelectorMode.BOX, + unit_of_measurement="decimals", + ), ), - vol.Required(CONF_UNIT_PREFIX, default="none"): selector.selector( - {"select": {"options": UNIT_PREFIXES}} + vol.Required(CONF_UNIT_PREFIX, default="none"): selector.SelectSelector( + selector.SelectSelectorConfig(options=UNIT_PREFIXES), ), - vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.selector( - {"select": {"options": TIME_UNITS, "mode": "dropdown"}} + vol.Required(CONF_UNIT_TIME, default=TIME_HOURS): selector.SelectSelector( + selector.SelectSelectorConfig( + options=TIME_UNITS, mode=selector.SelectSelectorMode.DROPDOWN + ), ), } ) diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index ba76556fc51..8cf5da5bc7c 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -62,12 +62,10 @@ UNIT_TIME = { TIME_DAYS: 24 * 60 * 60, } -ICON = "mdi:chart-histogram" - DEFAULT_ROUND = 3 PLATFORM_SCHEMA = vol.All( - cv.deprecated(CONF_UNIT_OF_MEASUREMENT), + cv.removed(CONF_UNIT_OF_MEASUREMENT), PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME): cv.string, @@ -76,7 +74,7 @@ PLATFORM_SCHEMA = vol.All( vol.Optional(CONF_ROUND_DIGITS, default=DEFAULT_ROUND): vol.Coerce(int), vol.Optional(CONF_UNIT_PREFIX, default=None): vol.In(UNIT_PREFIXES), vol.Optional(CONF_UNIT_TIME, default=TIME_HOURS): vol.In(UNIT_TIME), - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Remove(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_METHOD, default=METHOD_TRAPEZOIDAL): vol.In( INTEGRATION_METHODS ), @@ -107,7 +105,6 @@ async def async_setup_entry( round_digits=int(config_entry.options[CONF_ROUND_DIGITS]), source_entity=source_entity_id, unique_id=config_entry.entry_id, - unit_of_measurement=None, unit_prefix=unit_prefix, unit_time=config_entry.options[CONF_UNIT_TIME], ) @@ -128,7 +125,6 @@ async def async_setup_platform( round_digits=config[CONF_ROUND_DIGITS], source_entity=config[CONF_SOURCE_SENSOR], unique_id=config.get(CONF_UNIQUE_ID), - unit_of_measurement=config.get(CONF_UNIT_OF_MEASUREMENT), unit_prefix=config[CONF_UNIT_PREFIX], unit_time=config[CONF_UNIT_TIME], ) @@ -147,7 +143,6 @@ class IntegrationSensor(RestoreEntity, SensorEntity): round_digits: int, source_entity: str, unique_id: str | None, - unit_of_measurement: str | None, unit_prefix: str | None, unit_time: str, ) -> None: @@ -158,14 +153,17 @@ class IntegrationSensor(RestoreEntity, SensorEntity): self._state = None self._method = integration_method - self._name = name if name is not None else f"{source_entity} integral" + self._attr_name = name if name is not None else f"{source_entity} integral" self._unit_template = ( f"{'' if unit_prefix is None else unit_prefix}{{}}{unit_time}" ) - self._unit_of_measurement = unit_of_measurement + self._unit_of_measurement = None self._unit_prefix = UNIT_PREFIXES[unit_prefix] self._unit_time = UNIT_TIME[unit_time] self._attr_state_class = SensorStateClass.TOTAL + self._attr_icon = "mdi:chart-histogram" + self._attr_should_poll = False + self._attr_extra_state_attributes = {ATTR_SOURCE_ID: source_entity} async def async_added_to_hass(self): """Handle entity which will be added.""" @@ -174,7 +172,12 @@ class IntegrationSensor(RestoreEntity, SensorEntity): try: self._state = Decimal(state.state) except (DecimalException, ValueError) as err: - _LOGGER.warning("Could not restore last state: %s", err) + _LOGGER.warning( + "%s could not restore last state %s: %s", + self.entity_id, + state.state, + err, + ) else: self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS) if self._unit_of_measurement is None: @@ -193,10 +196,11 @@ class IntegrationSensor(RestoreEntity, SensorEntity): # or device_class. update_state = False - if self._unit_of_measurement is None: - unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - if unit is not None: - self._unit_of_measurement = self._unit_template.format(unit) + unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + if unit is not None: + new_unit_of_measurement = self._unit_template.format(unit) + if self._unit_of_measurement != new_unit_of_measurement: + self._unit_of_measurement = new_unit_of_measurement update_state = True if ( @@ -259,11 +263,6 @@ class IntegrationSensor(RestoreEntity, SensorEntity): ) ) - @property - def name(self): - """Return the name of the sensor.""" - return self._name - @property def native_value(self): """Return the state of the sensor.""" @@ -275,18 +274,3 @@ class IntegrationSensor(RestoreEntity, SensorEntity): def native_unit_of_measurement(self): """Return the unit the value is expressed in.""" return self._unit_of_measurement - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def extra_state_attributes(self): - """Return the state attributes of the sensor.""" - return {ATTR_SOURCE_ID: self._sensor_source_id} - - @property - def icon(self): - """Return the icon to use in the frontend.""" - return ICON diff --git a/homeassistant/components/vallox/translations/is.json b/homeassistant/components/integration/translations/bg.json similarity index 73% rename from homeassistant/components/vallox/translations/is.json rename to homeassistant/components/integration/translations/bg.json index 6878b2ecf11..35cfa0ad1d7 100644 --- a/homeassistant/components/vallox/translations/is.json +++ b/homeassistant/components/integration/translations/bg.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "name": "Nafn" + "name": "\u0418\u043c\u0435" } } } diff --git a/homeassistant/components/integration/translations/ca.json b/homeassistant/components/integration/translations/ca.json new file mode 100644 index 00000000000..bbe5e6e31b4 --- /dev/null +++ b/homeassistant/components/integration/translations/ca.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "M\u00e8tode d'integraci\u00f3", + "name": "Nom", + "round": "Precisi\u00f3", + "source": "Sensor d'entrada", + "unit_prefix": "Prefix m\u00e8tric", + "unit_time": "Unitat de temps" + }, + "data_description": { + "round": "Controla el nombre de d\u00edgits decimals a la sortida.", + "unit_prefix": "La sortida s'escalar\u00e0 segons el prefix m\u00e8tric seleccionat.", + "unit_time": "La sortida s'escalar\u00e0 segons la unitat de temps seleccionada." + }, + "description": "Crea un sensor que calcula la suma de Riemann que estima la integral d'un sensor.", + "title": "Afegeix sensor d'integraci\u00f3 de suma Riemann" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Precisi\u00f3" + }, + "data_description": { + "round": "Controla el nombre de d\u00edgits decimals a la sortida." + } + }, + "options": { + "data": { + "round": "Precisi\u00f3" + }, + "description": "La precisi\u00f3 controla el nombre de d\u00edgits decimals a la sortida." + } + } + }, + "title": "Integraci\u00f3 - Sensor integral de suma de Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/cs.json b/homeassistant/components/integration/translations/cs.json new file mode 100644 index 00000000000..3e7fdfca729 --- /dev/null +++ b/homeassistant/components/integration/translations/cs.json @@ -0,0 +1,41 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Metoda integrace", + "name": "Jm\u00e9no", + "round": "P\u0159esnost", + "source": "Vstupn\u00ed senzor", + "unit_time": "\u010casov\u00e1 jednotka" + }, + "data_description": { + "round": "Ur\u010duje po\u010det desetinn\u00fdch m\u00edst ve v\u00fdstupu.", + "unit_prefix": "V\u00fdstup bude \u0161k\u00e1lov\u00e1n podle vybran\u00e9ho metrick\u00e9ho prefixu.", + "unit_time": "V\u00fdstup bude \u0161k\u00e1lov\u00e1n podle zvolen\u00e9 \u010dasov\u00e9 jednotky." + }, + "description": "Vytvo\u0159\u00ed senzor, kter\u00fd vypo\u010d\u00edt\u00e1 Riemann\u016fv sou\u010det pro odhad integr\u00e1lu senzoru.", + "title": "P\u0159idat Riemann\u016fv sou\u010dtov\u00fd integr\u00e1ln\u00ed senzor" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "P\u0159esnost" + }, + "data_description": { + "round": "Ur\u010duje po\u010det desetinn\u00fdch m\u00edst ve v\u00fdstupu." + } + }, + "options": { + "data": { + "round": "P\u0159esnost" + }, + "description": "P\u0159esnost ur\u010duje po\u010det desetinn\u00fdch m\u00edst ve v\u00fdstupu." + } + } + }, + "title": "Integrace - Riemann\u016fv sou\u010dtov\u00fd integr\u00e1ln\u00ed senzor" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/de.json b/homeassistant/components/integration/translations/de.json new file mode 100644 index 00000000000..3013fa9d039 --- /dev/null +++ b/homeassistant/components/integration/translations/de.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Integrationsmethode", + "name": "Name", + "round": "Genauigkeit", + "source": "Eingangssensor", + "unit_prefix": "Metrisches Pr\u00e4fix", + "unit_time": "Zeiteinheit" + }, + "data_description": { + "round": "Steuert die Anzahl der Dezimalstellen in der Ausgabe.", + "unit_prefix": "Die Ausgabe wird entsprechend dem gew\u00e4hlten metrischen Pr\u00e4fix skaliert.", + "unit_time": "Die Ausgabe wird entsprechend der gew\u00e4hlten Zeiteinheit skaliert." + }, + "description": "Erstelle einen Sensor, der eine Riemann-Summe berechnet, um das Integral eines Sensors zu sch\u00e4tzen.", + "title": "Riemann-Summenintegralsensor hinzuf\u00fcgen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Genauigkeit" + }, + "data_description": { + "round": "Steuert die Anzahl der Dezimalstellen in der Ausgabe." + } + }, + "options": { + "data": { + "round": "Genauigkeit" + }, + "description": "Die Genauigkeit steuert die Anzahl der Dezimalstellen in der Ausgabe." + } + } + }, + "title": "Integration - Riemann-Summenintegralsensor" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/el.json b/homeassistant/components/integration/translations/el.json new file mode 100644 index 00000000000..e366137fce1 --- /dev/null +++ b/homeassistant/components/integration/translations/el.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "round": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "source": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "unit_prefix": "\u039c\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1", + "unit_time": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" + }, + "data_description": { + "round": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.", + "unit_prefix": "\u0397 \u03ad\u03be\u03bf\u03b4\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03ce\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1.", + "unit_time": "\u0397 \u03ad\u03be\u03bf\u03b4\u03bf\u03c2 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03c9\u03b8\u03b5\u03af \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5." + }, + "description": "\u0397 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf.\n\u03a4\u03bf \u03ac\u03b8\u03c1\u03bf\u03b9\u03c3\u03bc\u03b1 \u03b8\u03b1 \u03ba\u03bb\u03b9\u03bc\u03b1\u03ba\u03ce\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c3\u03cd\u03bc\u03c6\u03c9\u03bd\u03b1 \u03bc\u03b5 \u03c4\u03bf \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5\u03c4\u03c1\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03b8\u03b5\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03bf \u03c7\u03c1\u03cc\u03bd\u03bf \u03bf\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7\u03c2.", + "title": "\u039d\u03ad\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1" + }, + "data_description": { + "round": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf." + } + }, + "options": { + "data": { + "round": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1" + }, + "description": "\u0397 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf." + } + } + }, + "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7 - A\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2 \u03b1\u03b8\u03c1\u03bf\u03af\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2 Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/en.json b/homeassistant/components/integration/translations/en.json index 1ee047b447f..3174eab3f69 100644 --- a/homeassistant/components/integration/translations/en.json +++ b/homeassistant/components/integration/translations/en.json @@ -29,6 +29,12 @@ "data_description": { "round": "Controls the number of decimal digits in the output." } + }, + "options": { + "data": { + "round": "Precision" + }, + "description": "Precision controls the number of decimal digits in the output." } } }, diff --git a/homeassistant/components/integration/translations/et.json b/homeassistant/components/integration/translations/et.json new file mode 100644 index 00000000000..31901bbb6f6 --- /dev/null +++ b/homeassistant/components/integration/translations/et.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Integreerimismeetod", + "name": "Nimi", + "round": "T\u00e4psus", + "source": "Sisendandur", + "unit_prefix": "M\u00f5\u00f5diku eesliide", + "unit_time": "Aja\u00fchik" + }, + "data_description": { + "round": "K\u00fcmnendkohtade arv v\u00e4ljundis.", + "unit_prefix": "V\u00e4ljund skaleeritakse vastavalt valitud m\u00f5\u00f5diku prefiksile.", + "unit_time": "V\u00e4ljund skaleeritakse vastavalt valitud aja\u00fchikule." + }, + "description": "Looge andur, mis arvutab anduri integraali hindamiseks Riemanni summa.", + "title": "Lisa Riemanni summa integraalandur" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "T\u00e4psus" + }, + "data_description": { + "round": "M\u00e4\u00e4rab k\u00fcmnendkohtade arvu v\u00e4ljundis." + } + }, + "options": { + "data": { + "round": "T\u00e4psus" + }, + "description": "T\u00e4psus reguleerib k\u00fcmnendkohtade arvu v\u00e4ljundis." + } + } + }, + "title": "Sidumine - Riemanni integraalsumma andur" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/fr.json b/homeassistant/components/integration/translations/fr.json new file mode 100644 index 00000000000..33b5ab86e7f --- /dev/null +++ b/homeassistant/components/integration/translations/fr.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "M\u00e9thode de calcul de l'int\u00e9grale", + "name": "Nom", + "round": "Pr\u00e9cision", + "source": "Capteur d'entr\u00e9e", + "unit_prefix": "Pr\u00e9fixe m\u00e9trique", + "unit_time": "Unit\u00e9 de temps" + }, + "data_description": { + "round": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux dans la sortie.", + "unit_prefix": "La sortie sera mise \u00e0 l'\u00e9chelle en fonction du pr\u00e9fixe m\u00e9trique s\u00e9lectionn\u00e9.", + "unit_time": "La sortie sera mise \u00e0 l'\u00e9chelle en fonction de l'unit\u00e9 de temps s\u00e9lectionn\u00e9e." + }, + "description": "Cr\u00e9ez un capteur qui calcule une somme de Riemann afin d'estimer l'int\u00e9grale d'un autre capteur.", + "title": "Ajouter un capteur d'int\u00e9grale de Riemann" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Pr\u00e9cision" + }, + "data_description": { + "round": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux dans la sortie." + } + }, + "options": { + "data": { + "round": "Pr\u00e9cision" + }, + "description": "La pr\u00e9cision contr\u00f4le le nombre de chiffres d\u00e9cimaux dans la sortie." + } + } + }, + "title": "Int\u00e9grale \u2013\u00a0Capteur d'int\u00e9grale de Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/he.json b/homeassistant/components/integration/translations/he.json new file mode 100644 index 00000000000..4061da5f233 --- /dev/null +++ b/homeassistant/components/integration/translations/he.json @@ -0,0 +1,40 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\u05e9\u05d9\u05d8\u05ea \u05e9\u05d9\u05dc\u05d5\u05d1", + "name": "\u05e9\u05dd", + "round": "\u05d3\u05d9\u05d5\u05e7", + "source": "\u05d7\u05d9\u05d9\u05e9\u05df \u05e7\u05dc\u05d8", + "unit_prefix": "\u05e7\u05d9\u05d3\u05d5\u05de\u05ea \u05de\u05d8\u05e8\u05d9\u05ea", + "unit_time": "\u05d6\u05de\u05df \u05e9\u05d9\u05dc\u05d5\u05d1" + }, + "data_description": { + "round": "\u05e9\u05dc\u05d9\u05d8\u05d4 \u05d1\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05d4\u05e2\u05e9\u05e8\u05d5\u05e0\u05d9\u05d5\u05ea \u05d1\u05e4\u05dc\u05d8." + }, + "description": "\u05d3\u05d9\u05d5\u05e7 \u05e9\u05d5\u05dc\u05d8 \u05d1\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05d4\u05e2\u05e9\u05e8\u05d5\u05e0\u05d9\u05d5\u05ea \u05d1\u05e4\u05dc\u05d8.\n\u05d4\u05e1\u05db\u05d5\u05dd \u05d9\u05e9\u05ea\u05e0\u05d4 \u05d1\u05d4\u05ea\u05d0\u05dd \u05dc\u05e7\u05d9\u05d3\u05d5\u05de\u05ea \u05d4\u05de\u05d8\u05e8\u05d9\u05ea \u05e9\u05e0\u05d1\u05d7\u05e8\u05d4 \u05d5\u05d6\u05de\u05df \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.", + "title": "\u05d7\u05d9\u05d9\u05e9\u05df \u05e9\u05d9\u05dc\u05d5\u05d1 \u05d7\u05d3\u05e9" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\u05d3\u05d9\u05d5\u05e7" + }, + "data_description": { + "round": "\u05e9\u05dc\u05d9\u05d8\u05d4 \u05d1\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05d4\u05e2\u05e9\u05e8\u05d5\u05e0\u05d9\u05d5\u05ea \u05d1\u05e4\u05dc\u05d8." + } + }, + "options": { + "data": { + "round": "\u05d3\u05d9\u05d5\u05e7" + }, + "description": "\u05d3\u05d9\u05d5\u05e7 \u05e9\u05d5\u05dc\u05d8 \u05d1\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05d4\u05e2\u05e9\u05e8\u05d5\u05e0\u05d9\u05d5\u05ea \u05d1\u05e4\u05dc\u05d8." + } + } + }, + "title": "\u05e9\u05d9\u05dc\u05d5\u05d1 - \u05d7\u05d9\u05d9\u05e9\u05df \u05e1\u05db\u05d5\u05dd \u05d0\u05d9\u05e0\u05d8\u05d2\u05e8\u05dc\u05d9 \u05e9\u05dc \u05e8\u05d9\u05de\u05df" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/hu.json b/homeassistant/components/integration/translations/hu.json new file mode 100644 index 00000000000..c892e858b3f --- /dev/null +++ b/homeassistant/components/integration/translations/hu.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Integr\u00e1ci\u00f3s m\u00f3dszer", + "name": "Elnevez\u00e9s", + "round": "Pontoss\u00e1g", + "source": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "unit_prefix": "M\u00e9rt\u00e9kegys\u00e9g el\u0151tag", + "unit_time": "Id\u0151egys\u00e9g" + }, + "data_description": { + "round": "Az eredm\u00e9ny tizedesjegyeinek sz\u00e1ma.", + "unit_prefix": "A kimenet a kiv\u00e1lasztott m\u00e9rt\u00e9kegys\u00e9gnek megfelel\u0151en lesz sk\u00e1l\u00e1zva.", + "unit_time": "A kimenet a kiv\u00e1lasztott id\u0151egys\u00e9gnek megfelel\u0151en lesz sk\u00e1l\u00e1zva." + }, + "description": "Hozzon l\u00e9tre egy \u00faj \u00e9rz\u00e9kel\u0151t, amely Riemann-integr\u00e1lt sz\u00e1mol egy m\u00e1sik, megl\u00e9v\u0151 \u00e9rz\u00e9kel\u0151 alapj\u00e1n.", + "title": "Riemann-integr\u00e1l \u00e9rz\u00e9kel\u0151 l\u00e9trehoz\u00e1sa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Pontoss\u00e1g" + }, + "data_description": { + "round": "Az eredm\u00e9ny tizedesjegyeinek sz\u00e1ma." + } + }, + "options": { + "data": { + "round": "Pontoss\u00e1g" + }, + "description": "A pontoss\u00e1g hat\u00e1rozza meg az eredm\u00e9ny tizedesjegyeinek sz\u00e1m\u00e1t." + } + } + }, + "title": "Integr\u00e1ci\u00f3 - Riemann-integr\u00e1l" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/id.json b/homeassistant/components/integration/translations/id.json new file mode 100644 index 00000000000..d585a4409e9 --- /dev/null +++ b/homeassistant/components/integration/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Metode integrasi", + "name": "Nama", + "round": "Presisi", + "source": "Sensor input", + "unit_prefix": "Prefiks metrik", + "unit_time": "Unit waktu" + }, + "data_description": { + "round": "Mengontrol jumlah digit desimal dalam output.", + "unit_prefix": "Output akan diskalakan sesuai dengan prefiks metrik yang dipilih.", + "unit_time": "Output akan diskalakan sesuai dengan unit waktu dipilih." + }, + "description": "Buat sensor yang menghitung jumlah Riemann untuk memperkirakan integral dari sebuah sensor.", + "title": "Tambahkan sensor integral penjumlahan Riemann" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Presisi" + }, + "data_description": { + "round": "Mengontrol jumlah digit desimal dalam output." + } + }, + "options": { + "data": { + "round": "Presisi" + }, + "description": "Presisi mengontrol jumlah digit desimal pada output." + } + } + }, + "title": "Integrasi - Sensor jumlah integral Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/it.json b/homeassistant/components/integration/translations/it.json new file mode 100644 index 00000000000..a1904f45cd1 --- /dev/null +++ b/homeassistant/components/integration/translations/it.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Metodo di integrazione", + "name": "Nome", + "round": "Precisione", + "source": "Sensore di ingresso", + "unit_prefix": "Prefisso metrico", + "unit_time": "Unit\u00e0 di tempo" + }, + "data_description": { + "round": "Controlla il numero di cifre decimali nell'output.", + "unit_prefix": "L'output sar\u00e0 ridimensionato in base al prefisso della metrica selezionato.", + "unit_time": "L'output verr\u00e0 ridimensionato in base all'unit\u00e0 di tempo selezionata." + }, + "description": "Crea un sensore che calcoli una somma di Riemann per stimare l'integrale di un sensore.", + "title": "Aggiungi il sensore integrale della somma di Riemann" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Precisione" + }, + "data_description": { + "round": "Controlla il numero di cifre decimali nell'output." + } + }, + "options": { + "data": { + "round": "Precisione" + }, + "description": "Precisione controlla il numero di cifre decimali nell'uscita." + } + } + }, + "title": "Integrazione - Sensore integrale di somma Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/ja.json b/homeassistant/components/integration/translations/ja.json new file mode 100644 index 00000000000..5fac6ba692b --- /dev/null +++ b/homeassistant/components/integration/translations/ja.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\u7a4d\u7b97\u65b9\u6cd5", + "name": "\u540d\u524d", + "round": "\u7cbe\u5ea6", + "source": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "unit_prefix": "\u30e1\u30c8\u30ea\u30c3\u30af\u63a5\u982d\u8f9e", + "unit_time": "\u7a4d\u7b97\u6642\u9593" + }, + "data_description": { + "round": "\u51fa\u529b\u5024\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3002", + "unit_prefix": "\u51fa\u529b\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002", + "unit_time": "\u51fa\u529b\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u6642\u9593\u5358\u4f4d\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002" + }, + "description": "\u7cbe\u5ea6\u306f\u3001\u51fa\u529b\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002\n\u5408\u8a08\u306f\u3001\u9078\u629e\u3055\u308c\u305f\u5358\u4f4d\u306e\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9\u3068\u7a4d\u5206\u6642\u9593\u306b\u5f93\u3063\u3066\u30b9\u30b1\u30fc\u30ea\u30f3\u30b0\u3055\u308c\u307e\u3059\u3002", + "title": "\u65b0\u3057\u3044\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u30bb\u30f3\u30b5\u30fc" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\u7cbe\u5ea6" + }, + "data_description": { + "round": "\u51fa\u529b\u5024\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3002" + } + }, + "options": { + "data": { + "round": "\u7cbe\u5ea6" + }, + "description": "\u7cbe\u5ea6\u306f\u3001\u51fa\u529b\u306e\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002" + } + } + }, + "title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3 - \u30ea\u30fc\u30de\u30f3\u548c\u7a4d\u5206\u30bb\u30f3\u30b5\u30fc" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/nl.json b/homeassistant/components/integration/translations/nl.json new file mode 100644 index 00000000000..8753ee30ea7 --- /dev/null +++ b/homeassistant/components/integration/translations/nl.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Integratie methode", + "name": "Naam", + "round": "Precisie", + "source": "Invoer sensor", + "unit_prefix": "Metrisch voorvoegsel", + "unit_time": "Tijdseenheid" + }, + "data_description": { + "round": "Regelt het aantal decimale cijfers in de uitvoer.", + "unit_prefix": "De uitvoer wordt geschaald volgens het geselecteerde metrische voorvoegsel.", + "unit_time": "De output wordt geschaald volgens de geselecteerde tijdseenheid." + }, + "description": "Maak een sensor die een Riemann som berekent om de integraal van een sensor te schatten.", + "title": "Riemann som integrale sensor toevoegen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Precisie" + }, + "data_description": { + "round": "Regelt het aantal decimale cijfers in de uitvoer." + } + }, + "options": { + "data": { + "round": "Precisie" + }, + "description": "Precisie bepaalt het aantal decimale cijfers in de uitvoer." + } + } + }, + "title": "Integratie - Riemann som integrale sensor" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/no.json b/homeassistant/components/integration/translations/no.json new file mode 100644 index 00000000000..aafa60b5811 --- /dev/null +++ b/homeassistant/components/integration/translations/no.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Integrasjonsmetode", + "name": "Navn", + "round": "Presisjon", + "source": "Inngangssensor", + "unit_prefix": "Metrisk prefiks", + "unit_time": "Tidsenhet" + }, + "data_description": { + "round": "Styrer antall desimaler i utdataene.", + "unit_prefix": "Utdataene skaleres i henhold til det valgte metriske prefikset.", + "unit_time": "Utgangen vil bli skalert i henhold til den valgte tidsenheten." + }, + "description": "Lag en sensor som beregner en Riemann-sum for \u00e5 estimere integralet til en sensor.", + "title": "Legg til Riemann sum integral sensor" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Presisjon" + }, + "data_description": { + "round": "Styrer antall desimaler i utdataene." + } + }, + "options": { + "data": { + "round": "Presisjon" + }, + "description": "Presisjon styrer antall desimaler i utdataene." + } + } + }, + "title": "Integrasjon - Riemann sum integral sensor" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/pl.json b/homeassistant/components/integration/translations/pl.json new file mode 100644 index 00000000000..7971d97129c --- /dev/null +++ b/homeassistant/components/integration/translations/pl.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Metoda integracji", + "name": "Nazwa", + "round": "Precyzja", + "source": "Sensor wej\u015bciowy", + "unit_prefix": "Prefiks metryczny", + "unit_time": "Jednostka czasu" + }, + "data_description": { + "round": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych.", + "unit_prefix": "Dane wyj\u015bciowe b\u0119d\u0105 skalowane zgodnie z wybranym prefiksem metrycznym.", + "unit_time": "Dane wyj\u015bciowe b\u0119d\u0105 skalowane zgodnie z wybran\u0105 jednostk\u0105 czasu." + }, + "description": "Utw\u00f3rz sensor, kt\u00f3ry oblicza sum\u0119 Riemanna, aby oszacowa\u0107 ca\u0142k\u0119 sensora.", + "title": "Dodaj sensor ca\u0142kuj\u0105cy sum\u0119 Riemanna" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Precyzja" + }, + "data_description": { + "round": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych." + } + }, + "options": { + "data": { + "round": "Precyzja" + }, + "description": "Precyzja kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych." + } + } + }, + "title": "Integracja - czujnik ca\u0142kuj\u0105cy sum\u0119 Riemanna" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/pt-BR.json b/homeassistant/components/integration/translations/pt-BR.json new file mode 100644 index 00000000000..ae512b93fd4 --- /dev/null +++ b/homeassistant/components/integration/translations/pt-BR.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "M\u00e9todo de integra\u00e7\u00e3o", + "name": "Nome", + "round": "Precis\u00e3o", + "source": "Sensor fonte", + "unit_prefix": "Prefixo m\u00e9trico", + "unit_time": "Unidade de tempo" + }, + "data_description": { + "round": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda.", + "unit_prefix": "A sa\u00edda ser\u00e1 dimensionada de acordo com o prefixo m\u00e9trico selecionado.", + "unit_time": "A sa\u00edda ser\u00e1 dimensionada de acordo com a unidade de tempo selecionada." + }, + "description": "Crie um sensor que calcule uma soma de Riemann para estimar a integral de um sensor.", + "title": "Adicionar sensor de soma de Riemann" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Precis\u00e3o" + }, + "data_description": { + "round": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda." + } + }, + "options": { + "data": { + "round": "Precis\u00e3o" + }, + "description": "A precis\u00e3o controla o n\u00famero de d\u00edgitos decimais na sa\u00edda." + } + } + }, + "title": "Integra\u00e7\u00e3o - Sensor de soma de Riemann" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/ru.json b/homeassistant/components/integration/translations/ru.json new file mode 100644 index 00000000000..67891293d7b --- /dev/null +++ b/homeassistant/components/integration/translations/ru.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\u041c\u0435\u0442\u043e\u0434 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "round": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "source": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "unit_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0438", + "unit_time": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438" + }, + "data_description": { + "round": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439.", + "unit_prefix": "\u0414\u0430\u043d\u043d\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u043c \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u043c\u0435\u0442\u0440\u0438\u043a\u0438.", + "unit_time": "\u0414\u0430\u043d\u043d\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u0435\u0439 \u0432\u0440\u0435\u043c\u0435\u043d\u0438." + }, + "description": "\u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0441\u0443\u043c\u043c\u0443 \u0420\u0438\u043c\u0430\u043d\u0430 \u0434\u043b\u044f \u043e\u0446\u0435\u043d\u043a\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u043b\u0430 \u0441\u0435\u043d\u0441\u043e\u0440\u0430.", + "title": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u043b \u0420\u0438\u043c\u0430\u043d\u0430" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435" + }, + "data_description": { + "round": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439." + } + }, + "options": { + "data": { + "round": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435" + }, + "description": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439." + } + } + }, + "title": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u043b \u0420\u0438\u043c\u0430\u043d\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/tr.json b/homeassistant/components/integration/translations/tr.json new file mode 100644 index 00000000000..de99ebd3633 --- /dev/null +++ b/homeassistant/components/integration/translations/tr.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "Entegrasyon y\u00f6ntemi", + "name": "Ad", + "round": "Hassas", + "source": "Giri\u015f sens\u00f6r\u00fc", + "unit_prefix": "Metrik \u00f6neki", + "unit_time": "Zaman birimi" + }, + "data_description": { + "round": "\u00c7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder.", + "unit_prefix": "\u00c7\u0131kt\u0131, se\u00e7ilen metrik \u00f6nekine g\u00f6re \u00f6l\u00e7eklenecektir.", + "unit_time": "\u00c7\u0131kt\u0131, se\u00e7ilen zaman birimine g\u00f6re \u00f6l\u00e7eklenecektir." + }, + "description": "Bir sens\u00f6r\u00fcn integralini tahmin etmek i\u00e7in bir Riemann toplam\u0131n\u0131 hesaplayan bir sens\u00f6r olu\u015fturun.", + "title": "Riemann toplam integral sens\u00f6r\u00fcn\u00fc ekleyin" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "Hassas" + }, + "data_description": { + "round": "\u00c7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder." + } + }, + "options": { + "data": { + "round": "Hassas" + }, + "description": "Kesinlik, \u00e7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder." + } + } + }, + "title": "Entegrasyon - Riemann toplam integral sens\u00f6r\u00fc" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/zh-Hans.json b/homeassistant/components/integration/translations/zh-Hans.json new file mode 100644 index 00000000000..f7ebea98f4a --- /dev/null +++ b/homeassistant/components/integration/translations/zh-Hans.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\u79ef\u5206\u65b9\u6cd5", + "name": "\u540d\u79f0", + "round": "\u7cbe\u5ea6", + "source": "\u8f93\u5165\u4f20\u611f\u5668", + "unit_prefix": "\u5355\u4f4d\u524d\u7f00", + "unit_time": "\u65f6\u95f4\u5355\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002", + "unit_prefix": "\u8f93\u51fa\u503c\u5c06\u6839\u636e\u6240\u9009\u7684\u5355\u4f4d\u524d\u7f00\u8fdb\u884c\u7f29\u653e\u3002", + "unit_time": "\u8f93\u51fa\u503c\u5c06\u6839\u636e\u6240\u9009\u7684\u65f6\u95f4\u5355\u4f4d\u8fdb\u884c\u7f29\u653e\u3002" + }, + "description": "\u521b\u5efa\u4f20\u611f\u5668\u6765\u8ba1\u7b97\u53e6\u4e00\u4e2a\u4f20\u611f\u5668\u7684\u5b9a\u79ef\u5206\u3002", + "title": "\u6dfb\u52a0\u5b9a\u79ef\u5206\u4f20\u611f\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\u7cbe\u5ea6" + }, + "data_description": { + "round": "\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002" + } + }, + "options": { + "data": { + "round": "\u7cbe\u5ea6" + }, + "description": "\u7cbe\u5ea6\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002" + } + } + }, + "title": "\u5b9a\u79ef\u5206\u4f20\u611f\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/integration/translations/zh-Hant.json b/homeassistant/components/integration/translations/zh-Hant.json new file mode 100644 index 00000000000..2adbb3edc28 --- /dev/null +++ b/homeassistant/components/integration/translations/zh-Hant.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "method": "\u6574\u5408\u65b9\u5f0f", + "name": "\u540d\u7a31", + "round": "\u6e96\u78ba\u5ea6", + "source": "\u8f38\u5165\u611f\u6e2c\u5668", + "unit_prefix": "\u516c\u5236\u524d\u7db4", + "unit_time": "\u6642\u9593\u55ae\u4f4d" + }, + "data_description": { + "round": "\u63a7\u5236\u8f38\u51fa\u4e2d\u7684\u5c0f\u6578\u4f4d\u6578\u3002", + "unit_prefix": "\u8f38\u51fa\u5c07\u53d7\u6240\u9078\u64c7\u516c\u5236\u524d\u7db4\u800c\u8b8a\u5316\u3002", + "unit_time": "\u8f38\u51fa\u5c07\u53d7\u6240\u9078\u64c7\u6642\u9593\u55ae\u4f4d\u800c\u8b8a\u5316\u3002" + }, + "description": "\u65b0\u589e\u9810\u4f30\u8a08\u7b97\u9ece\u66fc\u548c\u7a4d\u5206\u611f\u6e2c\u5668\u6574\u5408\u4e4b\u611f\u6e2c\u5668\u3002", + "title": "\u65b0\u589e\u9ece\u66fc\u548c\u7a4d\u5206\u611f\u6e2c\u5668\u6574\u5408" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "round": "\u6e96\u78ba\u5ea6" + }, + "data_description": { + "round": "\u63a7\u5236\u8f38\u51fa\u4e2d\u7684\u5c0f\u6578\u4f4d\u6578\u3002" + } + }, + "options": { + "data": { + "round": "\u6e96\u78ba\u5ea6" + }, + "description": "\u7cbe\u6e96\u5ea6\u63a7\u5236\u8f38\u51fa\u4e2d\u7684\u5c0f\u6578\u4f4d\u6578\u3002" + } + } + }, + "title": "\u6574\u5408 - \u9ece\u66fc\u548c\u7a4d\u5206\u611f\u6e2c\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index 6774f235af9..e139626b88c 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -1,11 +1,14 @@ """The IntelliFire integration.""" from __future__ import annotations -from intellifire4py import IntellifireAsync +from aiohttp import ClientConnectionError +from intellifire4py import IntellifireAsync, IntellifireControlAsync +from intellifire4py.exceptions import LoginException from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, Platform +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DOMAIN, LOGGER from .coordinator import IntellifireDataUpdateCoordinator @@ -17,17 +20,35 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up IntelliFire from a config entry.""" LOGGER.debug("Setting up config entry: %s", entry.unique_id) - # Define the API Object - api_object = IntellifireAsync(entry.data[CONF_HOST]) + if CONF_USERNAME not in entry.data: + LOGGER.debug("Old config entry format detected: %s", entry.unique_id) + raise ConfigEntryAuthFailed + + # Define the API Objects + read_object = IntellifireAsync(entry.data[CONF_HOST]) + ift_control = IntellifireControlAsync( + fireplace_ip=entry.data[CONF_HOST], + ) + try: + await ift_control.login( + username=entry.data[CONF_USERNAME], + password=entry.data[CONF_PASSWORD], + ) + except (ConnectionError, ClientConnectionError) as err: + raise ConfigEntryNotReady from err + except LoginException as err: + raise ConfigEntryAuthFailed(err) from err + + finally: + await ift_control.close() # Define the update coordinator coordinator = IntellifireDataUpdateCoordinator( - hass=hass, - api=api_object, + hass=hass, read_api=read_object, control_api=ift_control ) + await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True diff --git a/homeassistant/components/intellifire/binary_sensor.py b/homeassistant/components/intellifire/binary_sensor.py index 837fc9e1bee..e72e75247b6 100644 --- a/homeassistant/components/intellifire/binary_sensor.py +++ b/homeassistant/components/intellifire/binary_sensor.py @@ -164,4 +164,4 @@ class IntellifireBinarySensor(IntellifireEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Use this to get the correct value.""" - return self.entity_description.value_fn(self.coordinator.api.data) + return self.entity_description.value_fn(self.coordinator.read_api.data) diff --git a/homeassistant/components/intellifire/config_flow.py b/homeassistant/components/intellifire/config_flow.py index a8d9fa135d2..6066d703729 100644 --- a/homeassistant/components/intellifire/config_flow.py +++ b/homeassistant/components/intellifire/config_flow.py @@ -5,12 +5,17 @@ from dataclasses import dataclass from typing import Any from aiohttp import ClientConnectionError -from intellifire4py import AsyncUDPFireplaceFinder, IntellifireAsync +from intellifire4py import ( + AsyncUDPFireplaceFinder, + IntellifireAsync, + IntellifireControlAsync, +) +from intellifire4py.exceptions import LoginException import voluptuous as vol from homeassistant import config_entries from homeassistant.components.dhcp import DhcpServiceInfo -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, LOGGER @@ -48,9 +53,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the Config Flow Handler.""" - self._config_context = {} + self._host: str = "" + self._serial: str = "" self._not_configured_hosts: list[DiscoveredHostInfo] = [] self._discovered_host: DiscoveredHostInfo + self._reauth_needed: DiscoveredHostInfo async def _find_fireplaces(self): """Perform UDP discovery.""" @@ -71,31 +78,103 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): LOGGER.debug("Configured Hosts: %s", configured_hosts) LOGGER.debug("Not Configured Hosts: %s", self._not_configured_hosts) - async def _async_validate_and_create_entry(self, host: str) -> FlowResult: - """Validate and create the entry.""" - self._async_abort_entries_match({CONF_HOST: host}) - serial = await validate_host_input(host) - await self.async_set_unique_id(serial, raise_on_progress=False) - self._abort_if_unique_id_configured(updates={CONF_HOST: host}) - return self.async_create_entry( - title=f"Fireplace {serial}", - data={CONF_HOST: host}, + async def validate_api_access_and_create_or_update( + self, *, host: str, username: str, password: str, serial: str + ): + """Validate username/password against api.""" + ift_control = IntellifireControlAsync(fireplace_ip=host) + + LOGGER.debug("Attempting login to iftapi with: %s", username) + # This can throw an error which will be handled above + try: + await ift_control.login(username=username, password=password) + await ift_control.get_username() + finally: + await ift_control.close() + + data = {CONF_HOST: host, CONF_PASSWORD: password, CONF_USERNAME: username} + + # Update or Create + existing_entry = await self.async_set_unique_id(serial) + if existing_entry: + self.hass.config_entries.async_update_entry(existing_entry, data=data) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + return self.async_create_entry(title=f"Fireplace {serial}", data=data) + + async def async_step_api_config( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure API access.""" + + errors = {} + control_schema = vol.Schema( + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } ) + if user_input is not None: + + control_schema = vol.Schema( + { + vol.Required( + CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") + ): str, + vol.Required( + CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") + ): str, + } + ) + + try: + return await self.validate_api_access_and_create_or_update( + host=self._host, + username=user_input[CONF_USERNAME], + password=user_input[CONF_PASSWORD], + serial=self._serial, + ) + + except (ConnectionError, ClientConnectionError): + errors["base"] = "iftapi_connect" + LOGGER.error( + "Could not connect to iftapi.net over https - verify connectivity" + ) + except LoginException: + errors["base"] = "api_error" + LOGGER.error("Invalid credentials for iftapi.net") + + return self.async_show_form( + step_id="api_config", errors=errors, data_schema=control_schema + ) + + async def _async_validate_ip_and_continue(self, host: str) -> FlowResult: + """Validate local config and continue.""" + self._async_abort_entries_match({CONF_HOST: host}) + self._serial = await validate_host_input(host) + await self.async_set_unique_id(self._serial, raise_on_progress=False) + self._abort_if_unique_id_configured(updates={CONF_HOST: host}) + # Store current data and jump to next stage + self._host = host + + return await self.async_step_api_config() + async def async_step_manual_device_entry(self, user_input=None): """Handle manual input of local IP configuration.""" + LOGGER.debug("STEP: manual_device_entry") errors = {} - host = user_input.get(CONF_HOST) if user_input else None + self._host = user_input.get(CONF_HOST) if user_input else None if user_input is not None: try: - return await self._async_validate_and_create_entry(host) + return await self._async_validate_ip_and_continue(self._host) except (ConnectionError, ClientConnectionError): errors["base"] = "cannot_connect" return self.async_show_form( step_id="manual_device_entry", errors=errors, - data_schema=vol.Schema({vol.Required(CONF_HOST, default=host): str}), + data_schema=vol.Schema({vol.Required(CONF_HOST, default=self._host): str}), ) async def async_step_pick_device( @@ -103,15 +182,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Pick which device to configure.""" errors = {} + LOGGER.debug("STEP: pick_device") if user_input is not None: if user_input[CONF_HOST] == MANUAL_ENTRY_STRING: return await self.async_step_manual_device_entry() try: - return await self._async_validate_and_create_entry( - user_input[CONF_HOST] - ) + return await self._async_validate_ip_and_continue(user_input[CONF_HOST]) except (ConnectionError, ClientConnectionError): errors["base"] = "cannot_connect" @@ -135,30 +213,44 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # Launch fireplaces discovery await self._find_fireplaces() - + LOGGER.debug("STEP: user") if self._not_configured_hosts: LOGGER.debug("Running Step: pick_device") return await self.async_step_pick_device() LOGGER.debug("Running Step: manual_device_entry") return await self.async_step_manual_device_entry() + async def async_step_reauth(self, user_input=None): + """Perform reauth upon an API authentication error.""" + LOGGER.debug("STEP: reauth") + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + + # populate the expected vars + self._serial = entry.unique_id + self._host = entry.data[CONF_HOST] + + placeholders = {CONF_HOST: self._host, "serial": self._serial} + self.context["title_placeholders"] = placeholders + return await self.async_step_api_config() + async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: """Handle DHCP Discovery.""" + LOGGER.debug("STEP: dhcp") # Run validation logic on ip host = discovery_info.ip self._async_abort_entries_match({CONF_HOST: host}) try: - serial = await validate_host_input(host) + self._serial = await validate_host_input(host) except (ConnectionError, ClientConnectionError): return self.async_abort(reason="not_intellifire_device") - await self.async_set_unique_id(serial) + await self.async_set_unique_id(self._serial) self._abort_if_unique_id_configured(updates={CONF_HOST: host}) - self._discovered_host = DiscoveredHostInfo(ip=host, serial=serial) + self._discovered_host = DiscoveredHostInfo(ip=host, serial=self._serial) - placeholders = {CONF_HOST: host, "serial": serial} + placeholders = {CONF_HOST: host, "serial": self._serial} self.context["title_placeholders"] = placeholders self._set_confirm_only() @@ -167,6 +259,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_dhcp_confirm(self, user_input=None): """Attempt to confirm.""" + LOGGER.debug("STEP: dhcp_confirm") # Add the hosts one by one host = self._discovered_host.ip serial = self._discovered_host.serial diff --git a/homeassistant/components/intellifire/const.py b/homeassistant/components/intellifire/const.py index d4aba5d2f8a..fe715c3ce8a 100644 --- a/homeassistant/components/intellifire/const.py +++ b/homeassistant/components/intellifire/const.py @@ -6,3 +6,5 @@ import logging DOMAIN = "intellifire" LOGGER = logging.getLogger(__package__) + +CONF_SERIAL = "serial" diff --git a/homeassistant/components/intellifire/coordinator.py b/homeassistant/components/intellifire/coordinator.py index 4d169e07a17..9b74bd81653 100644 --- a/homeassistant/components/intellifire/coordinator.py +++ b/homeassistant/components/intellifire/coordinator.py @@ -5,7 +5,11 @@ from datetime import timedelta from aiohttp import ClientConnectionError from async_timeout import timeout -from intellifire4py import IntellifireAsync, IntellifirePollData +from intellifire4py import ( + IntellifireAsync, + IntellifireControlAsync, + IntellifirePollData, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo @@ -17,7 +21,12 @@ from .const import DOMAIN, LOGGER class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData]): """Class to manage the polling of the fireplace API.""" - def __init__(self, hass: HomeAssistant, api: IntellifireAsync) -> None: + def __init__( + self, + hass: HomeAssistant, + read_api: IntellifireAsync, + control_api: IntellifireControlAsync, + ) -> None: """Initialize the Coordinator.""" super().__init__( hass, @@ -25,21 +34,27 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData name=DOMAIN, update_interval=timedelta(seconds=15), ) - self._api = api + self._read_api = read_api + self._control_api = control_api async def _async_update_data(self) -> IntellifirePollData: LOGGER.debug("Calling update loop on IntelliFire") async with timeout(100): try: - await self._api.poll() + await self._read_api.poll() except (ConnectionError, ClientConnectionError) as exception: raise UpdateFailed from exception - return self._api.data + return self._read_api.data @property - def api(self) -> IntellifireAsync: - """Return the API pointer.""" - return self._api + def read_api(self) -> IntellifireAsync: + """Return the Status API pointer.""" + return self._read_api + + @property + def control_api(self) -> IntellifireControlAsync: + """Return the control API.""" + return self._control_api @property def device_info(self) -> DeviceInfo: @@ -48,6 +63,7 @@ class IntellifireDataUpdateCoordinator(DataUpdateCoordinator[IntellifirePollData manufacturer="Hearth and Home", model="IFT-WFM", name="IntelliFire Fireplace", - identifiers={("IntelliFire", f"{self.api.data.serial}]")}, - sw_version=self.api.data.fw_ver_str, + identifiers={("IntelliFire", f"{self.read_api.data.serial}]")}, + sw_version=self.read_api.data.fw_ver_str, + configuration_url=f"http://{self.read_api.ip}/poll", ) diff --git a/homeassistant/components/intellifire/entity.py b/homeassistant/components/intellifire/entity.py index 6d20c015ab9..3c427250f19 100644 --- a/homeassistant/components/intellifire/entity.py +++ b/homeassistant/components/intellifire/entity.py @@ -22,6 +22,6 @@ class IntellifireEntity(CoordinatorEntity[IntellifireDataUpdateCoordinator]): self.entity_description = description # Set the Display name the User will see self._attr_name = f"Fireplace {description.name}" - self._attr_unique_id = f"{description.key}_{coordinator.api.data.serial}" + self._attr_unique_id = f"{description.key}_{coordinator.read_api.data.serial}" # Configure the Device Info self._attr_device_info = self.coordinator.device_info diff --git a/homeassistant/components/intellifire/sensor.py b/homeassistant/components/intellifire/sensor.py index b61ea443728..3bb3614f71e 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -150,4 +150,4 @@ class IntellifireSensor(IntellifireEntity, SensorEntity): @property def native_value(self) -> int | str | datetime | None: """Return the state.""" - return self.entity_description.value_fn(self.coordinator.api.data) + return self.entity_description.value_fn(self.coordinator.read_api.data) diff --git a/homeassistant/components/intellifire/strings.json b/homeassistant/components/intellifire/strings.json index 65877508db6..dd5c66adebd 100644 --- a/homeassistant/components/intellifire/strings.json +++ b/homeassistant/components/intellifire/strings.json @@ -5,23 +5,34 @@ "manual_device_entry": { "description": "Local Configuration", "data": { - "host": "[%key:common::config_flow::data::host%]" + "host": "Host (IP Address)" + } + }, + "api_config": { + "data": { + "username": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" } }, "dhcp_confirm": { "description": "Do you want to setup {host}\nSerial: {serial}?" }, "pick_device": { + "title": "Device Selection", + "description": "The following IntelliFire devices were discovered. Please select which you wish to configure.", "data": { "host": "[%key:common::config_flow::data::host%]" } } }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "api_error": "Login failed", + "iftapi_connect": "Error conecting to iftapi.net" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "not_intellifire_device": "Not an IntelliFire Device." } } diff --git a/homeassistant/components/intellifire/translations/bg.json b/homeassistant/components/intellifire/translations/bg.json index cbf1e2ae7c9..1a8dd460097 100644 --- a/homeassistant/components/intellifire/translations/bg.json +++ b/homeassistant/components/intellifire/translations/bg.json @@ -7,7 +7,22 @@ "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, + "flow_title": "{serial} ({host})", "step": { + "dhcp_confirm": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 {host}\n\u0421\u0435\u0440\u0438\u0435\u043d: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041b\u043e\u043a\u0430\u043b\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f" + }, + "pick_device": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" diff --git a/homeassistant/components/intellifire/translations/ca.json b/homeassistant/components/intellifire/translations/ca.json index 3673c1c60c6..9894ec4920a 100644 --- a/homeassistant/components/intellifire/translations/ca.json +++ b/homeassistant/components/intellifire/translations/ca.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "not_intellifire_device": "No \u00e9s un dispositiu IntelliFire.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { + "api_error": "Ha fallat l'inici de sessi\u00f3", "cannot_connect": "Ha fallat la connexi\u00f3", + "iftapi_connect": "S'ha produ\u00eft un error en connectar a iftapi.net", "unknown": "Error inesperat" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Contrasenya", + "username": "Correu electr\u00f2nic" + } + }, + "dhcp_confirm": { + "description": "Vols configurar {host}\nS\u00e8rie: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Amfitri\u00f3 (adre\u00e7a IP)" + }, + "description": "Configuraci\u00f3 local" + }, + "pick_device": { + "data": { + "host": "Amfitri\u00f3" + }, + "description": "S'han descobert els dispositius IntelliFire seg\u00fcents. Selecciona el que vulguis configurar.", + "title": "Selecci\u00f3 de dispositiu" + }, "user": { "data": { "host": "Amfitri\u00f3" diff --git a/homeassistant/components/intellifire/translations/cs.json b/homeassistant/components/intellifire/translations/cs.json index 5eac883adf0..48dbc509ea8 100644 --- a/homeassistant/components/intellifire/translations/cs.json +++ b/homeassistant/components/intellifire/translations/cs.json @@ -7,7 +7,18 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, + "flow_title": "{serial} ({host})", "step": { + "manual_device_entry": { + "data": { + "host": "Hostitel" + } + }, + "pick_device": { + "data": { + "host": "Hostitel" + } + }, "user": { "data": { "host": "Hostitel" diff --git a/homeassistant/components/intellifire/translations/de.json b/homeassistant/components/intellifire/translations/de.json index 6abbe1b2b27..f1c37a8a475 100644 --- a/homeassistant/components/intellifire/translations/de.json +++ b/homeassistant/components/intellifire/translations/de.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "not_intellifire_device": "Kein IntelliFire-Ger\u00e4t.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { + "api_error": "Login fehlgeschlagen", "cannot_connect": "Verbindung fehlgeschlagen", + "iftapi_connect": "Fehler beim Verbinden mit iftapi.net", "unknown": "Unerwarteter Fehler" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Passwort", + "username": "E-Mail" + } + }, + "dhcp_confirm": { + "description": "M\u00f6chtest du {host} einrichten\nSeriennummer: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Host (IP-Adresse)" + }, + "description": "Lokale Konfiguration" + }, + "pick_device": { + "data": { + "host": "Host" + }, + "description": "Die folgenden IntelliFire-Ger\u00e4te wurden gefunden. Bitte w\u00e4hle aus, welche du konfigurieren m\u00f6chtest.", + "title": "Ger\u00e4teauswahl" + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/intellifire/translations/el.json b/homeassistant/components/intellifire/translations/el.json index c7b88a9b3d8..fa72581a4a6 100644 --- a/homeassistant/components/intellifire/translations/el.json +++ b/homeassistant/components/intellifire/translations/el.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "not_intellifire_device": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae IntelliFire.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" }, "error": { + "api_error": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "iftapi_connect": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf iftapi.net", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "Email" + } + }, + "dhcp_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {host}\nSerial: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, + "description": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7" + }, + "pick_device": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + }, + "description": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03bf\u03b9 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 IntelliFire. \u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c0\u03bf\u03b9\u03b1 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" diff --git a/homeassistant/components/intellifire/translations/en.json b/homeassistant/components/intellifire/translations/en.json index 844d77427ca..f0c317efb93 100644 --- a/homeassistant/components/intellifire/translations/en.json +++ b/homeassistant/components/intellifire/translations/en.json @@ -2,23 +2,40 @@ "config": { "abort": { "already_configured": "Device is already configured", - "not_intellifire_device": "Not an IntelliFire Device." + "not_intellifire_device": "Not an IntelliFire Device.", + "reauth_successful": "Re-authentication was successful" }, "error": { - "cannot_connect": "Failed to connect" + "api_error": "Login failed", + "cannot_connect": "Failed to connect", + "iftapi_connect": "Error conecting to iftapi.net", + "unknown": "Unexpected error" }, "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Password", + "username": "Email" + } + }, "dhcp_confirm": { "description": "Do you want to setup {host}\nSerial: {serial}?" }, "manual_device_entry": { "data": { - "host": "Host" + "host": "Host (IP Address)" }, "description": "Local Configuration" }, "pick_device": { + "data": { + "host": "Host" + }, + "description": "The following IntelliFire devices were discovered. Please select which you wish to configure.", + "title": "Device Selection" + }, + "user": { "data": { "host": "Host" } diff --git a/homeassistant/components/intellifire/translations/et.json b/homeassistant/components/intellifire/translations/et.json index 939fa44224f..53c87f112a7 100644 --- a/homeassistant/components/intellifire/translations/et.json +++ b/homeassistant/components/intellifire/translations/et.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "not_intellifire_device": "See pole IntelliFire seade.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { + "api_error": "Sisselogimine nurjus", "cannot_connect": "\u00dchendamine nurjus", + "iftapi_connect": "\u00dchendumine iftapi.net'iga nurjus", "unknown": "Ootamatu t\u00f5rge" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Salas\u00f5na", + "username": "E-posti aadress" + } + }, + "dhcp_confirm": { + "description": "Kas h\u00e4\u00e4lestada {host}\nSeerianumber: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Host (IP aadress)" + }, + "description": "Kohalik konfiguratsioon" + }, + "pick_device": { + "data": { + "host": "Host" + }, + "description": "Avastati j\u00e4rgmised IntelliFire seadmed. Palun vali millist soovid seadistada.", + "title": "Seadme valik" + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/intellifire/translations/fr.json b/homeassistant/components/intellifire/translations/fr.json index 3b0762be825..5c478358345 100644 --- a/homeassistant/components/intellifire/translations/fr.json +++ b/homeassistant/components/intellifire/translations/fr.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "not_intellifire_device": "N'est pas un appareil IntelliFire.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { + "api_error": "La connexion a \u00e9chou\u00e9", "cannot_connect": "\u00c9chec de connexion", + "iftapi_connect": "Erreur lors de la connexion \u00e0 iftapi.net", "unknown": "Erreur inattendue" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Mot de passe", + "username": "Courriel" + } + }, + "dhcp_confirm": { + "description": "Voulez-vous configurer {host}\nNum\u00e9ro de s\u00e9rie\u00a0: {serial}\u00a0?" + }, + "manual_device_entry": { + "data": { + "host": "H\u00f4te (adresse IP)" + }, + "description": "Configuration locale" + }, + "pick_device": { + "data": { + "host": "H\u00f4te" + }, + "description": "Les appareils IntelliFire suivants ont \u00e9t\u00e9 d\u00e9couverts. Veuillez s\u00e9lectionner celui que vous souhaitez configurer.", + "title": "S\u00e9lection de l'appareil" + }, "user": { "data": { "host": "H\u00f4te" diff --git a/homeassistant/components/intellifire/translations/he.json b/homeassistant/components/intellifire/translations/he.json index 1699e0f8e19..e4b64392380 100644 --- a/homeassistant/components/intellifire/translations/he.json +++ b/homeassistant/components/intellifire/translations/he.json @@ -1,13 +1,30 @@ { "config": { "abort": { - "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" }, "error": { "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, "step": { + "api_config": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05d3\u05d5\u05d0\"\u05dc" + } + }, + "manual_device_entry": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7" + } + }, + "pick_device": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7" + } + }, "user": { "data": { "host": "\u05de\u05d0\u05e8\u05d7" diff --git a/homeassistant/components/intellifire/translations/hu.json b/homeassistant/components/intellifire/translations/hu.json index c46c7b02f5a..f5d11abe4c0 100644 --- a/homeassistant/components/intellifire/translations/hu.json +++ b/homeassistant/components/intellifire/translations/hu.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "not_intellifire_device": "Nem egy IntelliFire eszk\u00f6z.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "error": { + "api_error": "A bejelentkez\u00e9s sikertelen", "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "iftapi_connect": "Hiba az iftapi.net-hez val\u00f3 csatlakoz\u00e1sban", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" + } + }, + "dhcp_confirm": { + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani: {host}\nGy\u00e1ri sz\u00e1m: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "IP C\u00edm" + }, + "description": "Helyi konfigur\u00e1ci\u00f3" + }, + "pick_device": { + "data": { + "host": "C\u00edm" + }, + "description": "A k\u00f6vetkez\u0151 IntelliFire eszk\u00f6z\u00f6k \u00e9szlelve. K\u00e9rj\u00fck, v\u00e1lassza ki, melyiket szeretn\u00e9 konfigur\u00e1lni.", + "title": "Eszk\u00f6z v\u00e1laszt\u00e1sa" + }, "user": { "data": { "host": "C\u00edm" diff --git a/homeassistant/components/intellifire/translations/id.json b/homeassistant/components/intellifire/translations/id.json index c04f83e4b59..07ec946b6ba 100644 --- a/homeassistant/components/intellifire/translations/id.json +++ b/homeassistant/components/intellifire/translations/id.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "not_intellifire_device": "Bukan Perangkat IntelliFire.", + "reauth_successful": "Autentikasi ulang berhasil" }, "error": { + "api_error": "Gagal masuk", "cannot_connect": "Gagal terhubung", + "iftapi_connect": "Kesalahan saat menyambung ke iftapi.net", "unknown": "Kesalahan yang tidak diharapkan" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Kata Sandi", + "username": "Email" + } + }, + "dhcp_confirm": { + "description": "Ingin menyiapkan {host}\nSerial: ({serial})?" + }, + "manual_device_entry": { + "data": { + "host": "Host (Alamat IP)" + }, + "description": "Konfigurasi Lokal" + }, + "pick_device": { + "data": { + "host": "Host" + }, + "description": "Perangkat IntelliFire berikut ditemukan. Pilih yang ingin dikonfigurasikan.", + "title": "Pemilihan Perangkat" + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/intellifire/translations/it.json b/homeassistant/components/intellifire/translations/it.json index e8bfd780908..8689840c82c 100644 --- a/homeassistant/components/intellifire/translations/it.json +++ b/homeassistant/components/intellifire/translations/it.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "not_intellifire_device": "Non \u00e8 un dispositivo IntelliFire.", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { + "api_error": "Accesso non riuscito", "cannot_connect": "Impossibile connettersi", + "iftapi_connect": "Errore durante la connessione a iftapi.net", "unknown": "Errore imprevisto" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Password", + "username": "Email" + } + }, + "dhcp_confirm": { + "description": "Vuoi configurare {host}\n Seriale: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Host (indirizzo IP)" + }, + "description": "Configurazione locale" + }, + "pick_device": { + "data": { + "host": "Host" + }, + "description": "Sono stati rilevati i seguenti dispositivi IntelliFire. Seleziona quello che desideri configurare.", + "title": "Selezione del dispositivo" + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/intellifire/translations/ja.json b/homeassistant/components/intellifire/translations/ja.json index 718b4432290..c623f0f59cf 100644 --- a/homeassistant/components/intellifire/translations/ja.json +++ b/homeassistant/components/intellifire/translations/ja.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "not_intellifire_device": "IntelliFire\u30c7\u30d0\u30a4\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" }, "error": { + "api_error": "\u30ed\u30b0\u30a4\u30f3\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "iftapi_connect": "iftapi.net\u3078\u306e\u63a5\u7d9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "username": "E\u30e1\u30fc\u30eb" + } + }, + "dhcp_confirm": { + "description": "{host} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f\n\u30b7\u30ea\u30a2\u30eb: {serial}\uff1f" + }, + "manual_device_entry": { + "data": { + "host": "\u30db\u30b9\u30c8" + }, + "description": "\u30ed\u30fc\u30ab\u30eb\u8a2d\u5b9a" + }, + "pick_device": { + "data": { + "host": "\u30db\u30b9\u30c8" + }, + "description": "\u4ee5\u4e0b\u306eIntelliFire\u30c7\u30d0\u30a4\u30b9\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f\u3002\u8a2d\u5b9a\u3057\u305f\u3044\u3082\u306e\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "title": "\u30c7\u30d0\u30a4\u30b9\u306e\u9078\u629e" + }, "user": { "data": { "host": "\u30db\u30b9\u30c8" diff --git a/homeassistant/components/intellifire/translations/nl.json b/homeassistant/components/intellifire/translations/nl.json index 735a4df572f..a4f2b1c304a 100644 --- a/homeassistant/components/intellifire/translations/nl.json +++ b/homeassistant/components/intellifire/translations/nl.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "not_intellifire_device": "Niet een IntelliFire apparaat.", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { + "api_error": "Inloggen mislukt", "cannot_connect": "Kan geen verbinding maken", + "iftapi_connect": "Fout bij het verbinden met iftapi.net", "unknown": "Onverwachte fout" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Wachtwoord", + "username": "E-mail" + } + }, + "dhcp_confirm": { + "description": "Wilt u {host} instellen\n Serieel: {serial} ?" + }, + "manual_device_entry": { + "data": { + "host": "Host (IP-adres)" + }, + "description": "Lokale configuratie" + }, + "pick_device": { + "data": { + "host": "Host" + }, + "description": "De volgende IntelliFire-apparaten zijn ontdekt. Selecteer welke u wilt configureren.", + "title": "Apparaat selectie" + }, "user": { "data": { "host": "Host" diff --git a/homeassistant/components/intellifire/translations/no.json b/homeassistant/components/intellifire/translations/no.json index 12ee27af925..8175a085f30 100644 --- a/homeassistant/components/intellifire/translations/no.json +++ b/homeassistant/components/intellifire/translations/no.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "not_intellifire_device": "Ikke en IntelliFire-enhet.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { + "api_error": "Innlogging feilet", "cannot_connect": "Tilkobling mislyktes", + "iftapi_connect": "Feil ved tilkobling til iftapi.net", "unknown": "Uventet feil" }, + "flow_title": "{serial} ( {host} )", "step": { + "api_config": { + "data": { + "password": "Passord", + "username": "E-post" + } + }, + "dhcp_confirm": { + "description": "Vil du konfigurere {host}\n Serienummer: {serial} ?" + }, + "manual_device_entry": { + "data": { + "host": "Vert (IP-adresse)" + }, + "description": "Lokal konfigurasjon" + }, + "pick_device": { + "data": { + "host": "Vert" + }, + "description": "F\u00f8lgende IntelliFire-enheter ble oppdaget. Velg hvilken du \u00f8nsker \u00e5 konfigurere.", + "title": "Enhetsvalg" + }, "user": { "data": { "host": "Vert" diff --git a/homeassistant/components/intellifire/translations/pl.json b/homeassistant/components/intellifire/translations/pl.json index d455990b1f0..f63be88814f 100644 --- a/homeassistant/components/intellifire/translations/pl.json +++ b/homeassistant/components/intellifire/translations/pl.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "not_intellifire_device": "To nie jest urz\u0105dzenie IntelliFire.", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { + "api_error": "Logowanie nie powiod\u0142o si\u0119", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "iftapi_connect": "B\u0142\u0105d po\u0142\u0105czenia z iftapi.net", "unknown": "Nieoczekiwany b\u0142\u0105d" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Has\u0142o", + "username": "Adres e-mail" + } + }, + "dhcp_confirm": { + "description": "Czy chcesz skonfigurowa\u0107 {host}\nNumer seryjny: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Konfiguracja lokalna" + }, + "pick_device": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wykryto nast\u0119puj\u0105ce urz\u0105dzenia IntelliFire. Wybierz, kt\u00f3re chcesz skonfigurowa\u0107.", + "title": "Wyb\u00f3r urz\u0105dzenia" + }, "user": { "data": { "host": "Nazwa hosta lub adres IP" diff --git a/homeassistant/components/intellifire/translations/pt-BR.json b/homeassistant/components/intellifire/translations/pt-BR.json index ff6ede166a9..babcc22bd97 100644 --- a/homeassistant/components/intellifire/translations/pt-BR.json +++ b/homeassistant/components/intellifire/translations/pt-BR.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado" + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "not_intellifire_device": "N\u00e3o \u00e9 um dispositivo IntelliFire.", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" }, "error": { + "api_error": "Login falhou", "cannot_connect": "Falha ao conectar", + "iftapi_connect": "Erro ao conectar ao iftapi.net", "unknown": "Erro inesperado" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Senha", + "username": "Email" + } + }, + "dhcp_confirm": { + "description": "Voc\u00ea deseja configurar {host}\nSerial: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Host (endere\u00e7o IP)" + }, + "description": "Configura\u00e7\u00e3o local" + }, + "pick_device": { + "data": { + "host": "Nome do host" + }, + "description": "Os seguintes dispositivos IntelliFire foram descobertos. Por favor, selecione o que voc\u00ea deseja configura.", + "title": "Sele\u00e7\u00e3o de dispositivo" + }, "user": { "data": { "host": "Nome do host" diff --git a/homeassistant/components/intellifire/translations/ru.json b/homeassistant/components/intellifire/translations/ru.json index ffde0514cde..b48cd06b592 100644 --- a/homeassistant/components/intellifire/translations/ru.json +++ b/homeassistant/components/intellifire/translations/ru.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "not_intellifire_device": "\u041d\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e IntelliFire.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { + "api_error": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435 \u0443\u0434\u0430\u043b\u0430\u0441\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "iftapi_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a iftapi.net", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b" + } + }, + "dhcp_confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {host}\n\u0421\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "\u0425\u043e\u0441\u0442 (IP-\u0430\u0434\u0440\u0435\u0441)" + }, + "description": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "pick_device": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0411\u044b\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 IntelliFire. \u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u043a\u0430\u043a\u043e\u0435 \u0438\u0437 \u043d\u0438\u0445 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c.", + "title": "\u0412\u044b\u0431\u043e\u0440 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" diff --git a/homeassistant/components/intellifire/translations/tr.json b/homeassistant/components/intellifire/translations/tr.json index 05846c9d760..3a22e7c3680 100644 --- a/homeassistant/components/intellifire/translations/tr.json +++ b/homeassistant/components/intellifire/translations/tr.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "not_intellifire_device": "IntelliFire Cihaz\u0131 de\u011fil.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" }, "error": { + "api_error": "Oturum a\u00e7ma ba\u015far\u0131s\u0131z oldu", "cannot_connect": "Ba\u011flanma hatas\u0131", + "iftapi_connect": "iftapi.net'e ba\u011flan\u0131rken hata olu\u015ftu", "unknown": "Beklenmeyen hata" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "Parola", + "username": "E-posta" + } + }, + "dhcp_confirm": { + "description": "Kurulumu yapmak {host} ister misiniz?\nSeri No: {serial}?" + }, + "manual_device_entry": { + "data": { + "host": "Sunucu (IP Adresi)" + }, + "description": "Yerel Yap\u0131land\u0131rma" + }, + "pick_device": { + "data": { + "host": "Sunucu" + }, + "description": "A\u015fa\u011f\u0131daki IntelliFire cihazlar\u0131 ke\u015ffedildi. L\u00fctfen yap\u0131land\u0131rmak istedi\u011finizi se\u00e7in.", + "title": "Cihaz Se\u00e7imi" + }, "user": { "data": { "host": "Sunucu" diff --git a/homeassistant/components/intellifire/translations/zh-Hant.json b/homeassistant/components/intellifire/translations/zh-Hant.json index 9847ae248f7..107f9cca74c 100644 --- a/homeassistant/components/intellifire/translations/zh-Hant.json +++ b/homeassistant/components/intellifire/translations/zh-Hant.json @@ -1,13 +1,40 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "not_intellifire_device": "\u975e IntelliFire \u88dd\u7f6e\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { + "api_error": "\u767b\u5165\u5931\u6557", "cannot_connect": "\u9023\u7dda\u5931\u6557", + "iftapi_connect": "\u9023\u7dda\u81f3 iftapi.net \u932f\u8aa4", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, + "flow_title": "{serial} ({host})", "step": { + "api_config": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u96fb\u5b50\u90f5\u4ef6" + } + }, + "dhcp_confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {host}\n\u5e8f\u865f\uff1a{serial}\uff1f" + }, + "manual_device_entry": { + "data": { + "host": "\u4e3b\u6a5f\uff08IP \u4f4d\u5740\uff09" + }, + "description": "\u672c\u5730\u7aef\u8a2d\u5b9a" + }, + "pick_device": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u641c\u5c0b\u5230\u4ee5\u4e0b IntelliFire \u88dd\u7f6e\uff0c\u8acb\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684\u88dd\u7f6e\u3002", + "title": "\u88dd\u7f6e\u9078\u64c7" + }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef" diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py index 403cc91fcfa..0f7fe6b33ca 100644 --- a/homeassistant/components/intesishome/climate.py +++ b/homeassistant/components/intesishome/climate.py @@ -11,23 +11,15 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -68,12 +60,12 @@ class SwingSettings(NamedTuple): MAP_IH_TO_HVAC_MODE = { - "auto": HVAC_MODE_HEAT_COOL, - "cool": HVAC_MODE_COOL, - "dry": HVAC_MODE_DRY, - "fan": HVAC_MODE_FAN_ONLY, - "heat": HVAC_MODE_HEAT, - "off": HVAC_MODE_OFF, + "auto": HVACMode.HEAT_COOL, + "cool": HVACMode.COOL, + "dry": HVACMode.DRY, + "fan": HVACMode.FAN_ONLY, + "heat": HVACMode.HEAT, + "off": HVACMode.OFF, } MAP_HVAC_MODE_TO_IH = {v: k for k, v in MAP_IH_TO_HVAC_MODE.items()} @@ -95,11 +87,11 @@ MAP_SWING_TO_IH = { MAP_STATE_ICONS = { - HVAC_MODE_COOL: "mdi:snowflake", - HVAC_MODE_DRY: "mdi:water-off", - HVAC_MODE_FAN_ONLY: "mdi:fan", - HVAC_MODE_HEAT: "mdi:white-balance-sunny", - HVAC_MODE_HEAT_COOL: "mdi:cached", + HVACMode.COOL: "mdi:snowflake", + HVACMode.DRY: "mdi:water-off", + HVACMode.FAN_ONLY: "mdi:fan", + HVACMode.HEAT: "mdi:white-balance-sunny", + HVACMode.HEAT_COOL: "mdi:cached", } @@ -161,7 +153,7 @@ class IntesisAC(ClimateEntity): self._setpoint_step = 1 self._current_temp = None self._max_temp = None - self._hvac_mode_list = [] + self._attr_hvac_modes = [] self._min_temp = None self._target_temp = None self._outdoor_temp = None @@ -175,13 +167,13 @@ class IntesisAC(ClimateEntity): self._hvane = None self._power = False self._fan_speed = None - self._support = 0 + self._attr_supported_features = 0 self._power_consumption_heat = None self._power_consumption_cool = None # Setpoint support if controller.has_setpoint_control(ih_device_id): - self._support |= SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE # Setup swing list if controller.has_vertical_swing(ih_device_id): @@ -191,22 +183,22 @@ class IntesisAC(ClimateEntity): if SWING_HORIZONTAL in self._swing_list and SWING_VERTICAL in self._swing_list: self._swing_list.append(SWING_BOTH) if len(self._swing_list) > 1: - self._support |= SUPPORT_SWING_MODE + self._attr_supported_features |= ClimateEntityFeature.SWING_MODE # Setup fan speeds self._fan_modes = controller.get_fan_speed_list(ih_device_id) if self._fan_modes: - self._support |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE # Preset support if ih_device.get("climate_working_mode"): - self._support |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE # Setup HVAC modes if modes := controller.get_mode_list(ih_device_id): mode_list = [MAP_IH_TO_HVAC_MODE[mode] for mode in modes] - self._hvac_mode_list.extend(mode_list) - self._hvac_mode_list.append(HVAC_MODE_OFF) + self._attr_hvac_modes.extend(mode_list) + self._attr_hvac_modes.append(HVACMode.OFF) async def async_added_to_hass(self): """Subscribe to event updates.""" @@ -278,10 +270,10 @@ class IntesisAC(ClimateEntity): # Write updated temperature to HA state to avoid flapping (API confirmation is slow) self.async_write_ha_state() - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set operation mode.""" _LOGGER.debug("Setting %s to %s mode", self._device_type, hvac_mode) - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: self._power = False await self._controller.set_power_off(self._device_id) # Write changes to HA, API can be slow to push changes @@ -422,11 +414,6 @@ class IntesisAC(ClimateEntity): """Poll for updates if pyIntesisHome doesn't have a socket open.""" return False - @property - def hvac_modes(self): - """List of available operation modes.""" - return self._hvac_mode_list - @property def fan_mode(self): """Return whether the fan is on.""" @@ -466,18 +453,13 @@ class IntesisAC(ClimateEntity): return self._current_temp @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return the current mode of operation if unit is on.""" if self._power: return self._hvac_mode - return HVAC_MODE_OFF + return HVACMode.OFF @property def target_temperature(self): """Return the current setpoint temperature if unit is on.""" return self._target_temp - - @property - def supported_features(self): - """Return the list of supported features.""" - return self._support diff --git a/homeassistant/components/iotawatt/translations/hu.json b/homeassistant/components/iotawatt/translations/hu.json index 52d46f97a84..5f5d30136da 100644 --- a/homeassistant/components/iotawatt/translations/hu.json +++ b/homeassistant/components/iotawatt/translations/hu.json @@ -11,7 +11,7 @@ "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "Az IoTawatt eszk\u00f6z hiteles\u00edt\u00e9st ig\u00e9nyel. K\u00e9rj\u00fck, adja meg felhaszn\u00e1l\u00f3nev\u00e9t \u00e9s jelszav\u00e1t, majd kattintson a K\u00fcld\u00e9s gombra." + "description": "Az IoTawatt eszk\u00f6z hiteles\u00edt\u00e9st ig\u00e9nyel. K\u00e9rj\u00fck, adja meg felhaszn\u00e1l\u00f3nev\u00e9t \u00e9s jelszav\u00e1t, majd folytassa." }, "user": { "data": { diff --git a/homeassistant/components/iotawatt/translations/id.json b/homeassistant/components/iotawatt/translations/id.json index ef50c938292..bddda73fa50 100644 --- a/homeassistant/components/iotawatt/translations/id.json +++ b/homeassistant/components/iotawatt/translations/id.json @@ -11,7 +11,7 @@ "password": "Kata Sandi", "username": "Nama Pengguna" }, - "description": "Perangkat IoTawatt memerlukan otentikasi. Masukkan nama pengguna dan kata sandi dan klik tombol Kirim." + "description": "Perangkat IoTawatt memerlukan autentikasi. Masukkan nama pengguna dan kata sandi dan klik tombol Kirim." }, "user": { "data": { diff --git a/homeassistant/components/ipma/translations/hu.json b/homeassistant/components/ipma/translations/hu.json index 00ba66d2dd7..16653645052 100644 --- a/homeassistant/components/ipma/translations/hu.json +++ b/homeassistant/components/ipma/translations/hu.json @@ -9,7 +9,7 @@ "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", "mode": "M\u00f3d", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Portug\u00e1l Atmoszf\u00e9ra Int\u00e9zet", "title": "Elhelyezked\u00e9s" diff --git a/homeassistant/components/ipp/translations/it.json b/homeassistant/components/ipp/translations/it.json index c5a2a613eeb..96319641c6b 100644 --- a/homeassistant/components/ipp/translations/it.json +++ b/homeassistant/components/ipp/translations/it.json @@ -6,7 +6,7 @@ "connection_upgrade": "Impossibile connettersi alla stampante a causa della necessit\u00e0 dell'aggiornamento della connessione.", "ipp_error": "Si \u00e8 verificato un errore IPP.", "ipp_version_error": "Versione IPP non supportata dalla stampante.", - "parse_error": "Impossibile analizza la risposta dalla stampante.", + "parse_error": "Impossibile analizzare la risposta dalla stampante.", "unique_id_required": "Identificazione univoca del dispositivo mancante necessaria per l'individuazione." }, "error": { diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index b44904095de..b2eb67a6d93 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -24,7 +24,7 @@ "verify_ssl": "Weryfikacja certyfikatu SSL" }, "description": "Skonfiguruj drukark\u0119 za pomoc\u0105 protoko\u0142u IPP (Internet Printing Protocol), aby zintegrowa\u0107 j\u0105 z Home Assistantem.", - "title": "Po\u0142\u0105cz swoj\u0105 drukark\u0119" + "title": "Po\u0142\u0105czenie z Twoj\u0105 drukark\u0105" }, "zeroconf_confirm": { "description": "Czy chcesz doda\u0107 drukark\u0119 o nazwie `{name}` do Home Assistanta?", diff --git a/homeassistant/components/iqvia/manifest.json b/homeassistant/components/iqvia/manifest.json index dc91ede5461..50ddeb3bba7 100644 --- a/homeassistant/components/iqvia/manifest.json +++ b/homeassistant/components/iqvia/manifest.json @@ -3,7 +3,7 @@ "name": "IQVIA", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/iqvia", - "requirements": ["numpy==1.21.4", "pyiqvia==2021.11.0"], + "requirements": ["numpy==1.21.4", "pyiqvia==2022.04.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "loggers": ["pyiqvia"] diff --git a/homeassistant/components/iss/translations/hu.json b/homeassistant/components/iss/translations/hu.json index 1d75dd9ed3f..bfe593e9592 100644 --- a/homeassistant/components/iss/translations/hu.json +++ b/homeassistant/components/iss/translations/hu.json @@ -9,7 +9,7 @@ "data": { "show_on_map": "Megjelenjen a t\u00e9rk\u00e9pen?" }, - "description": "Szeretn\u00e9 konfigur\u00e1lni a Nemzetk\u00f6zi \u0170r\u00e1llom\u00e1st?" + "description": "Szeretn\u00e9 konfigur\u00e1lni a Nemzetk\u00f6zi \u0170r\u00e1llom\u00e1st (ISS)?" } } }, diff --git a/homeassistant/components/iss/translations/pt-BR.json b/homeassistant/components/iss/translations/pt-BR.json index 5e34b2eec1b..c69fefed0c5 100644 --- a/homeassistant/components/iss/translations/pt-BR.json +++ b/homeassistant/components/iss/translations/pt-BR.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "show_on_map": "Mostrar no mapa?" + "show_on_map": "Mostrar no mapa" }, "description": "Deseja configurar a Esta\u00e7\u00e3o Espacial Internacional (ISS)?" } @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "show_on_map": "Mostrar no mapa?" + "show_on_map": "Mostrar no mapa" } } } diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index ba152c5d840..ae48fc18b5b 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -288,14 +288,10 @@ async def async_unload_entry( hass_isy_data = hass.data[DOMAIN][entry.entry_id] - isy = hass_isy_data[ISY994_ISY] + isy: ISY = hass_isy_data[ISY994_ISY] - def _stop_auto_update() -> None: - """Stop the isy auto update.""" - _LOGGER.debug("ISY Stopping Event Stream and automatic updates") - isy.websocket.stop() - - await hass.async_add_executor_job(_stop_auto_update) + _LOGGER.debug("ISY Stopping Event Stream and automatic updates") + isy.websocket.stop() if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index 00a02e5c210..1276207f23c 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -23,12 +23,9 @@ from homeassistant.components.climate.const import ( FAN_AUTO, FAN_OFF, FAN_ON, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -59,10 +56,6 @@ from .const import ( from .entity import ISYNodeEntity from .helpers import convert_isy_value_to_hass, migrate_old_unique_ids -ISY_SUPPORTED_FEATURES = ( - SUPPORT_FAN_MODE | SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE -) - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -81,6 +74,13 @@ async def async_setup_entry( class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): """Representation of an ISY994 thermostat entity.""" + _attr_hvac_modes = ISY_HVAC_MODES + _attr_supported_features = ( + ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) + def __init__(self, node: Node) -> None: """Initialize the ISY Thermostat entity.""" super().__init__(node) @@ -95,11 +95,6 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): self._target_temp_low = 0 self._target_temp_high = 0 - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return ISY_SUPPORTED_FEATURES - @property def precision(self) -> float: """Return the precision of the system.""" @@ -124,10 +119,10 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): return int(humidity.value) @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" if not (hvac_mode := self._node.aux_properties.get(CMD_CLIMATE_MODE)): - return HVAC_MODE_OFF + return HVACMode.OFF # Which state values used depends on the mode property's UOM: uom = hvac_mode.uom @@ -138,15 +133,10 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): if self._node.protocol == PROTO_INSTEON else UOM_HVAC_MODE_GENERIC ) - return UOM_TO_STATES[uom].get(hvac_mode.value, HVAC_MODE_OFF) + return UOM_TO_STATES[uom].get(hvac_mode.value, HVACMode.OFF) @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes.""" - return ISY_HVAC_MODES - - @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" hvac_action = self._node.aux_properties.get(PROP_HEAT_COOL_STATE) if not hvac_action: @@ -168,9 +158,9 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return self.target_temperature_high - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return self.target_temperature_low return None @@ -209,9 +199,9 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if target_temp is not None: - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: target_temp_high = target_temp - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: target_temp_low = target_temp if target_temp_low is not None: await self._node.set_climate_setpoint_heat(int(target_temp_low)) @@ -231,7 +221,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): self._fan_mode = fan_mode self.async_write_ha_state() - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" _LOGGER.debug("Requested operation mode %s", hvac_mode) await self._node.set_climate_mode(HA_HVAC_TO_ISY.get(hvac_mode)) diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 866ec800402..dea4bce4eeb 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -203,7 +203,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> data_entry_flow.FlowResult: """Handle a discovered isy994 via dhcp.""" friendly_name = discovery_info.hostname - url = f"http://{discovery_info.ip}" + if friendly_name.startswith("polisy"): + url = f"http://{discovery_info.ip}:8080" + else: + url = f"http://{discovery_info.ip}" mac = discovery_info.macaddress isy_mac = ( f"{mac[0:2]}:{mac[2:4]}:{mac[4:6]}:{mac[6:8]}:{mac[8:10]}:{mac[10:12]}" diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index 8ca1ac786f8..bc463655f27 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -3,23 +3,14 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, + HVACAction, + HVACMode, ) from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, @@ -465,38 +456,38 @@ UOM_TO_STATES = { 27: "factory reset", }, UOM_HVAC_ACTIONS: { # Thermostat Heat/Cool State - 0: CURRENT_HVAC_IDLE, - 1: CURRENT_HVAC_HEAT, - 2: CURRENT_HVAC_COOL, - 3: CURRENT_HVAC_FAN, - 4: CURRENT_HVAC_HEAT, # Pending Heat - 5: CURRENT_HVAC_COOL, # Pending Cool + 0: HVACAction.IDLE.value, + 1: HVACAction.HEATING.value, + 2: HVACAction.COOLING.value, + 3: HVACAction.FAN.value, + 4: HVACAction.HEATING.value, # Pending Heat + 5: HVACAction.COOLING.value, # Pending Cool # >6 defined in ISY but not implemented, leaving for future expanision. - 6: CURRENT_HVAC_IDLE, - 7: CURRENT_HVAC_HEAT, - 8: CURRENT_HVAC_HEAT, - 9: CURRENT_HVAC_COOL, - 10: CURRENT_HVAC_HEAT, - 11: CURRENT_HVAC_HEAT, + 6: HVACAction.IDLE.value, + 7: HVACAction.HEATING.value, + 8: HVACAction.HEATING.value, + 9: HVACAction.COOLING.value, + 10: HVACAction.HEATING.value, + 11: HVACAction.HEATING.value, }, UOM_HVAC_MODE_GENERIC: { # Thermostat Mode - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, - 3: HVAC_MODE_AUTO, + 0: HVACMode.OFF.value, + 1: HVACMode.HEAT.value, + 2: HVACMode.COOL.value, + 3: HVACMode.AUTO.value, 4: PRESET_BOOST, 5: "resume", - 6: HVAC_MODE_FAN_ONLY, + 6: HVACMode.FAN_ONLY.value, 7: "furnace", - 8: HVAC_MODE_DRY, + 8: HVACMode.DRY.value, 9: "moist air", 10: "auto changeover", 11: "energy save heat", 12: "energy save cool", 13: PRESET_AWAY, - 14: HVAC_MODE_AUTO, - 15: HVAC_MODE_AUTO, - 16: HVAC_MODE_AUTO, + 14: HVACMode.AUTO.value, + 15: HVACMode.AUTO.value, + 16: HVACMode.AUTO.value, }, "68": { # Thermostat Fan Mode 0: FAN_AUTO, @@ -589,14 +580,14 @@ UOM_TO_STATES = { }, # 1-99 are percentage open }, UOM_HVAC_MODE_INSTEON: { # Insteon Thermostat Mode - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, - 3: HVAC_MODE_HEAT_COOL, - 4: HVAC_MODE_FAN_ONLY, - 5: HVAC_MODE_AUTO, # Program Auto - 6: HVAC_MODE_AUTO, # Program Heat-Set @ Local Device Only - 7: HVAC_MODE_AUTO, # Program Cool-Set @ Local Device Only + 0: HVACMode.OFF.value, + 1: HVACMode.HEAT.value, + 2: HVACMode.COOL.value, + 3: HVACMode.HEAT_COOL.value, + 4: HVACMode.FAN_ONLY.value, + 5: HVACMode.AUTO.value, # Program Auto + 6: HVACMode.AUTO.value, # Program Heat-Set @ Local Device Only + 7: HVACMode.AUTO.value, # Program Cool-Set @ Local Device Only }, UOM_FAN_MODES: {7: FAN_ON, 8: FAN_AUTO}, # Insteon Thermostat Fan Mode "115": { # Most recent On style action taken for lamp control @@ -617,21 +608,21 @@ UOM_TO_STATES = { } ISY_HVAC_MODES = [ - HVAC_MODE_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_AUTO, - HVAC_MODE_FAN_ONLY, + HVACMode.OFF, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.HEAT_COOL, + HVACMode.AUTO, + HVACMode.FAN_ONLY, ] HA_HVAC_TO_ISY = { - HVAC_MODE_OFF: "off", - HVAC_MODE_HEAT: "heat", - HVAC_MODE_COOL: "cool", - HVAC_MODE_HEAT_COOL: "auto", - HVAC_MODE_FAN_ONLY: "fan_only", - HVAC_MODE_AUTO: "program_auto", + HVACMode.OFF: "off", + HVACMode.HEAT: "heat", + HVACMode.COOL: "cool", + HVACMode.HEAT_COOL: "auto", + HVACMode.FAN_ONLY: "fan_only", + HVACMode.AUTO: "program_auto", } HA_FAN_TO_ISY = {FAN_ON: "on", FAN_AUTO: "auto"} diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index f00128b6d15..7c82ea2459a 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -8,10 +8,8 @@ from pyisy.constants import ISY_VALUE_UNKNOWN from homeassistant.components.cover import ( ATTR_POSITION, DOMAIN as COVER, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -48,6 +46,12 @@ async def async_setup_entry( class ISYCoverEntity(ISYNodeEntity, CoverEntity): """Representation of an ISY994 cover device.""" + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + @property def current_cover_position(self) -> int | None: """Return the current cover position.""" @@ -64,11 +68,6 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): return None return bool(self._node.status == 0) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - async def async_open_cover(self, **kwargs: Any) -> None: """Send the open cover command to the ISY994 cover device.""" val = 100 if self._node.uom == UOM_BARRIER else None diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index c215eebd382..9e264076d88 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -6,7 +6,7 @@ from typing import Any from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON -from homeassistant.components.fan import DOMAIN as FAN, SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import DOMAIN as FAN, FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -43,6 +43,8 @@ async def async_setup_entry( class ISYFanEntity(ISYNodeEntity, FanEntity): """Representation of an ISY994 fan device.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + @property def percentage(self) -> int | None: """Return the current speed percentage.""" @@ -87,11 +89,6 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): """Send the turn off command to the ISY994 fan device.""" await self._node.turn_off() - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED - class ISYFanProgramEntity(ISYProgramEntity, FanEntity): """Representation of an ISY994 fan program.""" diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 13df6d513b7..4606ef7e8de 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -7,11 +7,7 @@ from pyisy.constants import ISY_VALUE_UNKNOWN from pyisy.helpers import NodeProperty from pyisy.nodes import Node -from homeassistant.components.light import ( - DOMAIN as LIGHT, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import DOMAIN as LIGHT, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -51,6 +47,9 @@ async def async_setup_entry( class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): """Representation of an ISY994 light device.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, node: Node, restore_light_state: bool) -> None: """Initialize the ISY994 light device.""" super().__init__(node) @@ -108,11 +107,6 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): attribs[ATTR_LAST_BRIGHTNESS] = self._last_brightness return attribs - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - async def async_added_to_hass(self) -> None: """Restore last_brightness on restart.""" await super().async_added_to_hass() diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index d92226a4277..d131a150fb3 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -13,7 +13,8 @@ ], "dhcp": [ { "registered_devices": true }, - { "hostname": "isy*", "macaddress": "0021B9*" } + { "hostname": "isy*", "macaddress": "0021B9*" }, + { "hostname": "polisy*", "macaddress": "000DB9*" } ], "iot_class": "local_push", "loggers": ["pyisy"] diff --git a/homeassistant/components/itunes/media_player.py b/homeassistant/components/itunes/media_player.py index 1d25e5b3820..56b47a5c515 100644 --- a/homeassistant/components/itunes/media_player.py +++ b/homeassistant/components/itunes/media_player.py @@ -4,21 +4,14 @@ from __future__ import annotations import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SHUFFLE_SET, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, ) from homeassistant.const import ( CONF_HOST, @@ -42,20 +35,6 @@ DEFAULT_SSL = False DEFAULT_TIMEOUT = 10 DOMAIN = "itunes" -SUPPORT_ITUNES = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SEEK - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_TURN_OFF - | SUPPORT_SHUFFLE_SET -) - -SUPPORT_AIRPLAY = SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_TURN_OFF PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -214,6 +193,19 @@ def setup_platform( class ItunesDevice(MediaPlayerEntity): """Representation of an iTunes API instance.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SHUFFLE_SET + ) + def __init__(self, name, host, port, use_ssl, add_entities): """Initialize the iTunes device.""" self._name = name @@ -362,11 +354,6 @@ class ItunesDevice(MediaPlayerEntity): """Boolean if shuffle is enabled.""" return self.shuffled - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_ITUNES - def set_volume_level(self, volume): """Set volume level, range 0..1.""" response = self.client.set_volume(int(volume * 100)) @@ -417,6 +404,12 @@ class ItunesDevice(MediaPlayerEntity): class AirPlayDevice(MediaPlayerEntity): """Representation an AirPlay device via an iTunes API instance.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + ) + def __init__(self, device_id, client): """Initialize the AirPlay device.""" self._id = device_id @@ -491,11 +484,6 @@ class AirPlayDevice(MediaPlayerEntity): """Flag of media content that is supported.""" return MEDIA_TYPE_MUSIC - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_AIRPLAY - def set_volume_level(self, volume): """Set volume level, range 0..1.""" volume = int(volume * 100) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 6a6683958cf..419e1709b45 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -13,17 +13,10 @@ from homeassistant.components.climate.const import ( FAN_LOW, FAN_MEDIUM, FAN_TOP, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_ECO, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -139,24 +132,24 @@ class ControllerDevice(ClimateEntity): """Initialise ControllerDevice.""" self._controller = controller - self._supported_features = SUPPORT_FAN_MODE + self._attr_supported_features = ClimateEntityFeature.FAN_MODE # If mode RAS, or mode master with CtrlZone 13 then can set master temperature, # otherwise the unit determines which zone to use as target. See interface manual p. 8 if ( controller.ras_mode == "master" and controller.zone_ctrl == 13 ) or controller.ras_mode == "RAS": - self._supported_features |= SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE self._state_to_pizone = { - HVAC_MODE_COOL: Controller.Mode.COOL, - HVAC_MODE_HEAT: Controller.Mode.HEAT, - HVAC_MODE_HEAT_COOL: Controller.Mode.AUTO, - HVAC_MODE_FAN_ONLY: Controller.Mode.VENT, - HVAC_MODE_DRY: Controller.Mode.DRY, + HVACMode.COOL: Controller.Mode.COOL, + HVACMode.HEAT: Controller.Mode.HEAT, + HVACMode.HEAT_COOL: Controller.Mode.AUTO, + HVACMode.FAN_ONLY: Controller.Mode.VENT, + HVACMode.DRY: Controller.Mode.DRY, } if controller.free_air_enabled: - self._supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE self._fan_to_pizone = {} for fan in controller.fan_modes: @@ -267,11 +260,6 @@ class ControllerDevice(ClimateEntity): """ return False - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return self._supported_features - @property def temperature_unit(self) -> str: """Return the unit of measurement which this thermostat uses.""" @@ -300,7 +288,7 @@ class ControllerDevice(ClimateEntity): ), "control_zone": self._controller.zone_ctrl, "control_zone_name": self.control_zone_name, - # Feature SUPPORT_TARGET_TEMPERATURE controls both displaying target temp & setting it + # Feature ClimateEntityFeature.TARGET_TEMPERATURE controls both displaying target temp & setting it # As the feature is turned off for zone control, report target temp as extra state attribute "control_zone_setpoint": show_temp( self.hass, @@ -311,12 +299,12 @@ class ControllerDevice(ClimateEntity): } @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" if not self._controller.is_on: - return HVAC_MODE_OFF + return HVACMode.OFF if (mode := self._controller.mode) == Controller.Mode.FREE_AIR: - return HVAC_MODE_FAN_ONLY + return HVACMode.FAN_ONLY for (key, value) in self._state_to_pizone.items(): if value == mode: return key @@ -324,11 +312,11 @@ class ControllerDevice(ClimateEntity): @property @_return_on_connection_error([]) - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" if self._controller.free_air: - return [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] - return [HVAC_MODE_OFF, *self._state_to_pizone] + return [HVACMode.OFF, HVACMode.FAN_ONLY] + return [HVACMode.OFF, *self._state_to_pizone] @property @_return_on_connection_error(PRESET_NONE) @@ -355,7 +343,7 @@ class ControllerDevice(ClimateEntity): @property def control_zone_name(self): """Return the zone that currently controls the AC unit (if target temp not set by controller).""" - if self._supported_features & SUPPORT_TARGET_TEMPERATURE: + if self._attr_supported_features & ClimateEntityFeature.TARGET_TEMPERATURE: return None zone_ctrl = self._controller.zone_ctrl zone = next((z for z in self.zones.values() if z.zone_index == zone_ctrl), None) @@ -366,7 +354,7 @@ class ControllerDevice(ClimateEntity): @property def control_zone_setpoint(self) -> float | None: """Return the temperature setpoint of the zone that currently controls the AC unit (if target temp not set by controller).""" - if self._supported_features & SUPPORT_TARGET_TEMPERATURE: + if self._attr_supported_features & ClimateEntityFeature.TARGET_TEMPERATURE: return None zone_ctrl = self._controller.zone_ctrl zone = next((z for z in self.zones.values() if z.zone_index == zone_ctrl), None) @@ -378,7 +366,7 @@ class ControllerDevice(ClimateEntity): @_return_on_connection_error() def target_temperature(self) -> float | None: """Return the temperature we try to reach (either from control zone or master unit).""" - if self._supported_features & SUPPORT_TARGET_TEMPERATURE: + if self._attr_supported_features & ClimateEntityFeature.TARGET_TEMPERATURE: return self._controller.temp_setpoint return self.control_zone_setpoint @@ -425,7 +413,7 @@ class ControllerDevice(ClimateEntity): async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - if not self.supported_features & SUPPORT_TARGET_TEMPERATURE: + if not self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE: self.async_schedule_update_ha_state(True) return if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None: @@ -436,9 +424,9 @@ class ControllerDevice(ClimateEntity): fan = self._fan_to_pizone[fan_mode] await self.wrap_and_catch(self._controller.set_fan(fan)) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.wrap_and_catch(self._controller.set_on(False)) return if not self._controller.is_on: @@ -468,19 +456,19 @@ class ZoneDevice(ClimateEntity): self._zone = zone self._name = zone.name.title() - self._supported_features = 0 + self._attr_supported_features = 0 if zone.type != Zone.Type.AUTO: self._state_to_pizone = { - HVAC_MODE_OFF: Zone.Mode.CLOSE, - HVAC_MODE_FAN_ONLY: Zone.Mode.OPEN, + HVACMode.OFF: Zone.Mode.CLOSE, + HVACMode.FAN_ONLY: Zone.Mode.OPEN, } else: self._state_to_pizone = { - HVAC_MODE_OFF: Zone.Mode.CLOSE, - HVAC_MODE_FAN_ONLY: Zone.Mode.OPEN, - HVAC_MODE_HEAT_COOL: Zone.Mode.AUTO, + HVACMode.OFF: Zone.Mode.CLOSE, + HVACMode.FAN_ONLY: Zone.Mode.OPEN, + HVACMode.HEAT_COOL: Zone.Mode.AUTO, } - self._supported_features |= SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE self._attr_device_info = DeviceInfo( identifiers={(IZONE, controller.unique_id, zone.index)}, @@ -550,8 +538,8 @@ class ZoneDevice(ClimateEntity): def supported_features(self): """Return the list of supported features.""" if self._zone.mode == Zone.Mode.AUTO: - return self._supported_features - return self._supported_features & ~SUPPORT_TARGET_TEMPERATURE + return self._attr_supported_features + return self._attr_supported_features & ~ClimateEntityFeature.TARGET_TEMPERATURE @property def temperature_unit(self): @@ -564,7 +552,7 @@ class ZoneDevice(ClimateEntity): return PRECISION_TENTHS @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" mode = self._zone.mode for (key, value) in self._state_to_pizone.items(): @@ -573,7 +561,7 @@ class ZoneDevice(ClimateEntity): return None @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return list(self._state_to_pizone) @@ -635,7 +623,7 @@ class ZoneDevice(ClimateEntity): if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None: await self._controller.wrap_and_catch(self._zone.set_temp_setpoint(temp)) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" mode = self._state_to_pizone[hvac_mode] await self._controller.wrap_and_catch(self._zone.set_mode(mode)) diff --git a/homeassistant/components/kaleidescape/media_player.py b/homeassistant/components/kaleidescape/media_player.py index 080db5524fe..da70643f8ee 100644 --- a/homeassistant/components/kaleidescape/media_player.py +++ b/homeassistant/components/kaleidescape/media_player.py @@ -1,5 +1,4 @@ """Kaleidescape Media Player.""" - from __future__ import annotations import logging @@ -7,15 +6,9 @@ from typing import TYPE_CHECKING from kaleidescape import const as kaleidescape_const -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.util.dt import utcnow @@ -26,8 +19,6 @@ from .entity import KaleidescapeEntity if TYPE_CHECKING: from datetime import datetime - from kaleidescape import Device as KaleidescapeDevice - from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -41,15 +32,6 @@ KALEIDESCAPE_PLAYING_STATES = [ KALEIDESCAPE_PAUSED_STATES = [kaleidescape_const.PLAY_STATUS_PAUSED] -SUPPORTED_FEATURES = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK -) _LOGGER = logging.getLogger(__name__) @@ -65,10 +47,15 @@ async def async_setup_entry( class KaleidescapeMediaPlayer(KaleidescapeEntity, MediaPlayerEntity): """Representation of a Kaleidescape device.""" - def __init__(self, device: KaleidescapeDevice) -> None: - """Initialize media player.""" - super().__init__(device) - self._attr_supported_features = SUPPORTED_FEATURES + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + ) async def async_turn_on(self) -> None: """Send leave standby command.""" diff --git a/homeassistant/components/kaleidescape/translations/cs.json b/homeassistant/components/kaleidescape/translations/cs.json new file mode 100644 index 00000000000..deb0693b00d --- /dev/null +++ b/homeassistant/components/kaleidescape/translations/cs.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "flow_title": "{model} ({name})" + } +} \ No newline at end of file diff --git a/homeassistant/components/kaleidescape/translations/hu.json b/homeassistant/components/kaleidescape/translations/hu.json index 953de8119ed..cd8b00e18a3 100644 --- a/homeassistant/components/kaleidescape/translations/hu.json +++ b/homeassistant/components/kaleidescape/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", "unsupported": "Nem t\u00e1mogatott eszk\u00f6z" }, diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index d061ee3a04b..9ed669c3201 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -13,17 +13,8 @@ import voluptuous as vol from homeassistant.components.media_player import ( PLATFORM_SCHEMA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -238,6 +229,20 @@ class KefMediaPlayer(MediaPlayerEntity): self._dsp = None self._update_dsp_task_remover = None + self._attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.NEXT_TRACK # only in Bluetooth and Wifi + | MediaPlayerEntityFeature.PAUSE # only in Bluetooth and Wifi + | MediaPlayerEntityFeature.PLAY # only in Bluetooth and Wifi + | MediaPlayerEntityFeature.PREVIOUS_TRACK # only in Bluetooth and Wifi + ) + if supports_on: + self._attr_supported_features |= MediaPlayerEntityFeature.TURN_ON + @property def name(self): """Return the name of the device.""" @@ -283,25 +288,6 @@ class KefMediaPlayer(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - support_kef = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_SELECT_SOURCE - | SUPPORT_TURN_OFF - | SUPPORT_NEXT_TRACK # only in Bluetooth and Wifi - | SUPPORT_PAUSE # only in Bluetooth and Wifi - | SUPPORT_PLAY # only in Bluetooth and Wifi - | SUPPORT_PREVIOUS_TRACK # only in Bluetooth and Wifi - ) - if self._supports_on: - support_kef |= SUPPORT_TURN_ON - - return support_kef - @property def source(self): """Name of the current input source.""" diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index c9ec825bebc..2e1fd61ad43 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -10,12 +10,10 @@ from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode from homeassistant import config_entries from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_OFF, PRESET_AWAY, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -141,9 +139,9 @@ class KNXClimate(KnxEntity, ClimateEntity): """Initialize of a KNX climate device.""" super().__init__(_create_climate(xknx, config)) self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE if self.preset_modes: - self._attr_supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE self._attr_target_temperature_step = self._device.temperature_step self._attr_unique_id = ( f"{self._device.temperature.group_address_state}_" @@ -151,13 +149,9 @@ class KNXClimate(KnxEntity, ClimateEntity): f"{self._device.target_temperature.group_address}_" f"{self._device._setpoint_shift.group_address}" ) - self.default_hvac_mode: str = config[ClimateSchema.CONF_DEFAULT_CONTROLLER_MODE] - - async def async_update(self) -> None: - """Request a state update from KNX bus.""" - await self._device.sync() - if self._device.mode is not None: - await self._device.mode.sync() + self.default_hvac_mode: HVACMode = config[ + ClimateSchema.CONF_DEFAULT_CONTROLLER_MODE + ] @property def current_temperature(self) -> float | None: @@ -183,16 +177,16 @@ class KNXClimate(KnxEntity, ClimateEntity): async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: - return - await self._device.set_target_temperature(temperature) - self.async_write_ha_state() + temperature = kwargs.get(ATTR_TEMPERATURE) + if temperature is not None: + await self._device.set_target_temperature(temperature) + self.async_write_ha_state() @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: - return HVAC_MODE_OFF + return HVACMode.OFF if self._device.mode is not None and self._device.mode.supports_controller_mode: return CONTROLLER_MODES.get( self._device.mode.controller_mode.value, self.default_hvac_mode @@ -200,9 +194,9 @@ class KNXClimate(KnxEntity, ClimateEntity): return self.default_hvac_mode @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation/controller modes.""" - ha_controller_modes: list[str | None] = [] + ha_controller_modes: list[HVACMode | None] = [] if self._device.mode is not None: for knx_controller_mode in self._device.mode.controller_modes: ha_controller_modes.append( @@ -212,30 +206,30 @@ class KNXClimate(KnxEntity, ClimateEntity): if self._device.supports_on_off: if not ha_controller_modes: ha_controller_modes.append(self.default_hvac_mode) - ha_controller_modes.append(HVAC_MODE_OFF) + ha_controller_modes.append(HVACMode.OFF) hvac_modes = list(set(filter(None, ha_controller_modes))) return hvac_modes if hvac_modes else [self.default_hvac_mode] @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. """ if self._device.supports_on_off and not self._device.is_on: - return CURRENT_HVAC_OFF + return HVACAction.OFF if self._device.is_active is False: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if ( self._device.mode is not None and self._device.mode.supports_controller_mode ) or self._device.is_active: - return CURRENT_HVAC_ACTIONS.get(self.hvac_mode, CURRENT_HVAC_IDLE) + return CURRENT_HVAC_ACTIONS.get(self.hvac_mode, HVACAction.IDLE) return None - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set operation mode.""" - if self._device.supports_on_off and hvac_mode == HVAC_MODE_OFF: + if self._device.supports_on_off and hvac_mode == HVACMode.OFF: await self._device.turn_off() else: if self._device.supports_on_off and not self._device.is_on: @@ -254,7 +248,7 @@ class KNXClimate(KnxEntity, ClimateEntity): def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ if self._device.mode is not None and self._device.mode.supports_operation_mode: return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY) @@ -264,7 +258,7 @@ class KNXClimate(KnxEntity, ClimateEntity): def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. - Requires SUPPORT_PRESET_MODE. + Requires ClimateEntityFeature.PRESET_MODE. """ if self._device.mode is None: return None diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index e45eb3a87a1..d6516d1d4ef 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -24,7 +24,6 @@ from .const import ( CONF_KNX_DEFAULT_RATE_LIMIT, CONF_KNX_DEFAULT_STATE_UPDATER, CONF_KNX_INDIVIDUAL_ADDRESS, - CONF_KNX_INITIAL_CONNECTION_TYPES, CONF_KNX_KNXKEY_FILENAME, CONF_KNX_KNXKEY_PASSWORD, CONF_KNX_LOCAL_IP, @@ -44,18 +43,19 @@ from .const import ( DOMAIN, KNXConfigEntryData, ) +from .schema import ia_validator, ip_v4_validator CONF_KNX_GATEWAY: Final = "gateway" CONF_MAX_RATE_LIMIT: Final = 60 CONF_DEFAULT_LOCAL_IP: Final = "0.0.0.0" -DEFAULT_ENTRY_DATA: KNXConfigEntryData = { - CONF_KNX_STATE_UPDATER: CONF_KNX_DEFAULT_STATE_UPDATER, - CONF_KNX_RATE_LIMIT: CONF_KNX_DEFAULT_RATE_LIMIT, - CONF_KNX_INDIVIDUAL_ADDRESS: XKNX.DEFAULT_ADDRESS, - CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, - CONF_KNX_MCAST_PORT: DEFAULT_MCAST_PORT, -} +DEFAULT_ENTRY_DATA = KNXConfigEntryData( + individual_address=XKNX.DEFAULT_ADDRESS, + multicast_group=DEFAULT_MCAST_GRP, + multicast_port=DEFAULT_MCAST_PORT, + state_updater=CONF_KNX_DEFAULT_STATE_UPDATER, + rate_limit=CONF_KNX_DEFAULT_RATE_LIMIT, +) CONF_KNX_TUNNELING_TYPE: Final = "tunneling_type" CONF_KNX_LABEL_TUNNELING_TCP: Final = "TCP" @@ -63,10 +63,14 @@ CONF_KNX_LABEL_TUNNELING_TCP_SECURE: Final = "TCP with IP Secure" CONF_KNX_LABEL_TUNNELING_UDP: Final = "UDP" CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK: Final = "UDP with route back / NAT mode" -_IA_SELECTOR = selector.selector({"text": {}}) -_IP_SELECTOR = selector.selector({"text": {}}) +_IA_SELECTOR = selector.TextSelector() +_IP_SELECTOR = selector.TextSelector() _PORT_SELECTOR = vol.All( - selector.selector({"number": {"min": 1, "max": 65535, "mode": "box"}}), + selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=65535, mode=selector.NumberSelectorMode.BOX + ), + ), vol.Coerce(int), ) @@ -101,10 +105,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: connection_type = user_input[CONF_KNX_CONNECTION_TYPE] if connection_type == CONF_KNX_AUTOMATIC: - entry_data: KNXConfigEntryData = { - **DEFAULT_ENTRY_DATA, # type: ignore[misc] - CONF_KNX_CONNECTION_TYPE: user_input[CONF_KNX_CONNECTION_TYPE], - } + entry_data = DEFAULT_ENTRY_DATA | KNXConfigEntryData( + connection_type=CONF_KNX_AUTOMATIC + ) return self.async_create_entry( title=CONF_KNX_AUTOMATIC.capitalize(), data=entry_data, @@ -118,13 +121,15 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_manual_tunnel() - errors: dict = {} - supported_connection_types = CONF_KNX_INITIAL_CONNECTION_TYPES.copy() - gateways = await scan_for_gateways() - - if gateways: - # add automatic only if a gateway responded - supported_connection_types.insert(0, CONF_KNX_AUTOMATIC) + supported_connection_types = { + CONF_KNX_TUNNELING: CONF_KNX_TUNNELING.capitalize(), + CONF_KNX_ROUTING: CONF_KNX_ROUTING.capitalize(), + } + if gateways := await scan_for_gateways(): + # add automatic at first position only if a gateway responded + supported_connection_types = { + CONF_KNX_AUTOMATIC: CONF_KNX_AUTOMATIC.capitalize() + } | supported_connection_types self._found_tunnels = [ gateway for gateway in gateways if gateway.supports_tunnelling ] @@ -132,10 +137,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): fields = { vol.Required(CONF_KNX_CONNECTION_TYPE): vol.In(supported_connection_types) } - - return self.async_show_form( - step_id="type", data_schema=vol.Schema(fields), errors=errors - ) + return self.async_show_form(step_id="type", data_schema=vol.Schema(fields)) async def async_step_tunnel(self, user_input: dict | None = None) -> FlowResult: """Select a tunnel from a list. Will be skipped if the gateway scan was unsuccessful or if only one gateway was found.""" @@ -164,37 +166,48 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict | None = None ) -> FlowResult: """Manually configure tunnel connection parameters. Fields default to preselected gateway if one was found.""" + errors: dict = {} + if user_input is not None: - connection_type = user_input[CONF_KNX_TUNNELING_TYPE] + try: + _host = ip_v4_validator(user_input[CONF_HOST], multicast=False) + except vol.Invalid: + errors[CONF_HOST] = "invalid_ip_address" - entry_data: KNXConfigEntryData = { - **DEFAULT_ENTRY_DATA, # type: ignore[misc] - CONF_HOST: user_input[CONF_HOST], - CONF_PORT: user_input[CONF_PORT], - CONF_KNX_ROUTE_BACK: ( - connection_type == CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK - ), - CONF_KNX_LOCAL_IP: user_input.get(CONF_KNX_LOCAL_IP), - CONF_KNX_CONNECTION_TYPE: ( - CONF_KNX_TUNNELING_TCP - if connection_type == CONF_KNX_LABEL_TUNNELING_TCP - else CONF_KNX_TUNNELING - ), - } + if _local_ip := user_input.get(CONF_KNX_LOCAL_IP): + try: + _local_ip = ip_v4_validator(_local_ip, multicast=False) + except vol.Invalid: + errors[CONF_KNX_LOCAL_IP] = "invalid_ip_address" - if connection_type == CONF_KNX_LABEL_TUNNELING_TCP_SECURE: - self._tunneling_config = entry_data - return self.async_show_menu( - step_id="secure_tunneling", - menu_options=["secure_knxkeys", "secure_manual"], + if not errors: + connection_type = user_input[CONF_KNX_TUNNELING_TYPE] + entry_data = DEFAULT_ENTRY_DATA | KNXConfigEntryData( + host=_host, + port=user_input[CONF_PORT], + route_back=( + connection_type == CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK + ), + local_ip=_local_ip, + connection_type=( + CONF_KNX_TUNNELING_TCP + if connection_type == CONF_KNX_LABEL_TUNNELING_TCP + else CONF_KNX_TUNNELING + ), ) - return self.async_create_entry( - title=f"{CONF_KNX_TUNNELING.capitalize()} @ {user_input[CONF_HOST]}", - data=entry_data, - ) + if connection_type == CONF_KNX_LABEL_TUNNELING_TCP_SECURE: + self._tunneling_config = entry_data + return self.async_show_menu( + step_id="secure_tunneling", + menu_options=["secure_knxkeys", "secure_manual"], + ) + + return self.async_create_entry( + title=f"Tunneling @ {_host}", + data=entry_data, + ) - errors: dict = {} connection_methods: list[str] = [ CONF_KNX_LABEL_TUNNELING_TCP, CONF_KNX_LABEL_TUNNELING_UDP, @@ -231,33 +244,32 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: assert self._tunneling_config - entry_data: KNXConfigEntryData = { - **self._tunneling_config, # type: ignore[misc] - CONF_KNX_SECURE_USER_ID: user_input[CONF_KNX_SECURE_USER_ID], - CONF_KNX_SECURE_USER_PASSWORD: user_input[ - CONF_KNX_SECURE_USER_PASSWORD - ], - CONF_KNX_SECURE_DEVICE_AUTHENTICATION: user_input[ - CONF_KNX_SECURE_DEVICE_AUTHENTICATION - ], - CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING_TCP_SECURE, - } + entry_data = self._tunneling_config | KNXConfigEntryData( + connection_type=CONF_KNX_TUNNELING_TCP_SECURE, + device_authentication=user_input[CONF_KNX_SECURE_DEVICE_AUTHENTICATION], + user_id=user_input[CONF_KNX_SECURE_USER_ID], + user_password=user_input[CONF_KNX_SECURE_USER_PASSWORD], + ) return self.async_create_entry( - title=f"Secure {CONF_KNX_TUNNELING.capitalize()} @ {self._tunneling_config[CONF_HOST]}", + title=f"Secure Tunneling @ {self._tunneling_config[CONF_HOST]}", data=entry_data, ) fields = { vol.Required(CONF_KNX_SECURE_USER_ID, default=2): vol.All( - selector.selector({"number": {"min": 1, "max": 127, "mode": "box"}}), + selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=127, mode=selector.NumberSelectorMode.BOX + ), + ), vol.Coerce(int), ), - vol.Required(CONF_KNX_SECURE_USER_PASSWORD): selector.selector( - {"text": {"type": "password"}} + vol.Required(CONF_KNX_SECURE_USER_PASSWORD): selector.TextSelector( + selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD), ), - vol.Required(CONF_KNX_SECURE_DEVICE_AUTHENTICATION): selector.selector( - {"text": {"type": "password"}} + vol.Required(CONF_KNX_SECURE_DEVICE_AUTHENTICATION): selector.TextSelector( + selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD), ), } @@ -272,37 +284,33 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: + assert self._tunneling_config + storage_key = CONST_KNX_STORAGE_KEY + user_input[CONF_KNX_KNXKEY_FILENAME] try: - assert self._tunneling_config - storage_key: str = ( - CONST_KNX_STORAGE_KEY + user_input[CONF_KNX_KNXKEY_FILENAME] - ) load_key_ring( - self.hass.config.path( - STORAGE_DIR, - storage_key, - ), - user_input[CONF_KNX_KNXKEY_PASSWORD], + path=self.hass.config.path(STORAGE_DIR, storage_key), + password=user_input[CONF_KNX_KNXKEY_PASSWORD], + ) + except FileNotFoundError: + errors[CONF_KNX_KNXKEY_FILENAME] = "file_not_found" + except InvalidSignature: + errors[CONF_KNX_KNXKEY_PASSWORD] = "invalid_signature" + + if not errors: + entry_data = self._tunneling_config | KNXConfigEntryData( + connection_type=CONF_KNX_TUNNELING_TCP_SECURE, + knxkeys_filename=storage_key, + knxkeys_password=user_input[CONF_KNX_KNXKEY_PASSWORD], ) - entry_data: KNXConfigEntryData = { - **self._tunneling_config, # type: ignore[misc] - CONF_KNX_KNXKEY_FILENAME: storage_key, - CONF_KNX_KNXKEY_PASSWORD: user_input[CONF_KNX_KNXKEY_PASSWORD], - CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING_TCP_SECURE, - } return self.async_create_entry( - title=f"Secure {CONF_KNX_TUNNELING.capitalize()} @ {self._tunneling_config[CONF_HOST]}", + title=f"Secure Tunneling @ {self._tunneling_config[CONF_HOST]}", data=entry_data, ) - except InvalidSignature: - errors["base"] = "invalid_signature" - except FileNotFoundError: - errors["base"] = "file_not_found" fields = { - vol.Required(CONF_KNX_KNXKEY_FILENAME): selector.selector({"text": {}}), - vol.Required(CONF_KNX_KNXKEY_PASSWORD): selector.selector({"text": {}}), + vol.Required(CONF_KNX_KNXKEY_FILENAME): selector.TextSelector(), + vol.Required(CONF_KNX_KNXKEY_PASSWORD): selector.TextSelector(), } return self.async_show_form( @@ -311,33 +319,55 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_routing(self, user_input: dict | None = None) -> FlowResult: """Routing setup.""" - if user_input is not None: - return self.async_create_entry( - title=CONF_KNX_ROUTING.capitalize(), - data={ - **DEFAULT_ENTRY_DATA, - CONF_KNX_MCAST_GRP: user_input[CONF_KNX_MCAST_GRP], - CONF_KNX_MCAST_PORT: user_input[CONF_KNX_MCAST_PORT], - CONF_KNX_INDIVIDUAL_ADDRESS: user_input[ - CONF_KNX_INDIVIDUAL_ADDRESS - ], - CONF_KNX_LOCAL_IP: user_input.get(CONF_KNX_LOCAL_IP), - CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING, - }, - ) - errors: dict = {} + _individual_address = ( + user_input[CONF_KNX_INDIVIDUAL_ADDRESS] + if user_input + else XKNX.DEFAULT_ADDRESS + ) + _multicast_group = ( + user_input[CONF_KNX_MCAST_GRP] if user_input else DEFAULT_MCAST_GRP + ) + + if user_input is not None: + try: + ia_validator(_individual_address) + except vol.Invalid: + errors[CONF_KNX_INDIVIDUAL_ADDRESS] = "invalid_individual_address" + try: + ip_v4_validator(_multicast_group, multicast=True) + except vol.Invalid: + errors[CONF_KNX_MCAST_GRP] = "invalid_ip_address" + if _local_ip := user_input.get(CONF_KNX_LOCAL_IP): + try: + ip_v4_validator(_local_ip, multicast=False) + except vol.Invalid: + errors[CONF_KNX_LOCAL_IP] = "invalid_ip_address" + + if not errors: + entry_data = DEFAULT_ENTRY_DATA | KNXConfigEntryData( + connection_type=CONF_KNX_ROUTING, + individual_address=_individual_address, + multicast_group=_multicast_group, + multicast_port=user_input[CONF_KNX_MCAST_PORT], + local_ip=_local_ip, + ) + return self.async_create_entry( + title=CONF_KNX_ROUTING.capitalize(), data=entry_data + ) + fields = { vol.Required( - CONF_KNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS + CONF_KNX_INDIVIDUAL_ADDRESS, default=_individual_address ): _IA_SELECTOR, - vol.Required(CONF_KNX_MCAST_GRP, default=DEFAULT_MCAST_GRP): _IP_SELECTOR, + vol.Required(CONF_KNX_MCAST_GRP, default=_multicast_group): _IP_SELECTOR, vol.Required( CONF_KNX_MCAST_PORT, default=DEFAULT_MCAST_PORT ): _PORT_SELECTOR, } if self.show_advanced_options: + # Optional with default doesn't work properly in flow UI fields[vol.Optional(CONF_KNX_LOCAL_IP)] = _IP_SELECTOR return self.async_show_form( @@ -383,7 +413,7 @@ class KNXOptionsFlowHandler(OptionsFlow): vol.Required( CONF_KNX_INDIVIDUAL_ADDRESS, default=self.current_config[CONF_KNX_INDIVIDUAL_ADDRESS], - ): selector.selector({"text": {}}), + ): selector.TextSelector(), vol.Required( CONF_KNX_MCAST_GRP, default=self.current_config.get(CONF_KNX_MCAST_GRP, DEFAULT_MCAST_GRP), @@ -416,7 +446,7 @@ class KNXOptionsFlowHandler(OptionsFlow): CONF_KNX_DEFAULT_STATE_UPDATER, ), ) - ] = selector.selector({"boolean": {}}) + ] = selector.BooleanSelector() data_schema[ vol.Required( CONF_KNX_RATE_LIMIT, @@ -426,14 +456,12 @@ class KNXOptionsFlowHandler(OptionsFlow): ), ) ] = vol.All( - selector.selector( - { - "number": { - "min": 1, - "max": CONF_MAX_RATE_LIMIT, - "mode": "box", - } - } + selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=CONF_MAX_RATE_LIMIT, + mode=selector.NumberSelectorMode.BOX, + ), ), vol.Coerce(int), ) @@ -477,38 +505,34 @@ class KNXOptionsFlowHandler(OptionsFlow): last_step=True, ) - entry_data = { - **DEFAULT_ENTRY_DATA, - **self.general_settings, - CONF_KNX_LOCAL_IP: self.general_settings.get(CONF_KNX_LOCAL_IP) - if self.general_settings.get(CONF_KNX_LOCAL_IP) != CONF_DEFAULT_LOCAL_IP - else None, - CONF_HOST: self.current_config.get(CONF_HOST, ""), - } + _local_ip = self.general_settings.get(CONF_KNX_LOCAL_IP) + entry_data = ( + DEFAULT_ENTRY_DATA + | self.general_settings + | KNXConfigEntryData( + host=self.current_config.get(CONF_HOST, ""), + local_ip=_local_ip if _local_ip != CONF_DEFAULT_LOCAL_IP else None, + ) + ) if user_input is not None: connection_type = user_input[CONF_KNX_TUNNELING_TYPE] - entry_data = { - **entry_data, - CONF_HOST: user_input[CONF_HOST], - CONF_PORT: user_input[CONF_PORT], - CONF_KNX_ROUTE_BACK: ( - connection_type == CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK - ), - CONF_KNX_CONNECTION_TYPE: ( + entry_data = entry_data | KNXConfigEntryData( + host=user_input[CONF_HOST], + port=user_input[CONF_PORT], + route_back=(connection_type == CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK), + connection_type=( CONF_KNX_TUNNELING_TCP if connection_type == CONF_KNX_LABEL_TUNNELING_TCP else CONF_KNX_TUNNELING ), - } + ) entry_title = str(entry_data[CONF_KNX_CONNECTION_TYPE]).capitalize() if entry_data[CONF_KNX_CONNECTION_TYPE] == CONF_KNX_TUNNELING: - entry_title = f"{CONF_KNX_TUNNELING.capitalize()} @ {entry_data[CONF_HOST]}" + entry_title = f"Tunneling @ {entry_data[CONF_HOST]}" if entry_data[CONF_KNX_CONNECTION_TYPE] == CONF_KNX_TUNNELING_TCP: - entry_title = ( - f"{CONF_KNX_TUNNELING.capitalize()} (TCP) @ {entry_data[CONF_HOST]}" - ) + entry_title = f"Tunneling @ {entry_data[CONF_HOST]} (TCP)" self.hass.config_entries.async_update_entry( self.config_entry, diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index efe091f22a9..df8f0de3216 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -1,24 +1,17 @@ """Constants for the KNX integration.""" +from __future__ import annotations + from enum import Enum from typing import Final, TypedDict from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_COMFORT, PRESET_ECO, PRESET_NONE, PRESET_SLEEP, + HVACAction, + HVACMode, ) from homeassistant.const import Platform @@ -68,7 +61,6 @@ CONF_RESET_AFTER: Final = "reset_after" CONF_RESPOND_TO_READ: Final = "respond_to_read" CONF_STATE_ADDRESS: Final = "state_address" CONF_SYNC_STATE: Final = "sync_state" -CONF_KNX_INITIAL_CONNECTION_TYPES: Final = [CONF_KNX_TUNNELING, CONF_KNX_ROUTING] # yaml config merged with config entry data DATA_KNX_CONFIG: Final = "knx_config" @@ -84,7 +76,7 @@ class KNXConfigEntryData(TypedDict, total=False): connection_type: str individual_address: str - local_ip: str + local_ip: str | None multicast_group: str multicast_port: int route_back: bool @@ -126,20 +118,20 @@ SUPPORTED_PLATFORMS: Final = [ # Map KNX controller modes to HA modes. This list might not be complete. CONTROLLER_MODES: Final = { # Map DPT 20.105 HVAC control modes - "Auto": HVAC_MODE_AUTO, - "Heat": HVAC_MODE_HEAT, - "Cool": HVAC_MODE_COOL, - "Off": HVAC_MODE_OFF, - "Fan only": HVAC_MODE_FAN_ONLY, - "Dry": HVAC_MODE_DRY, + "Auto": HVACMode.AUTO, + "Heat": HVACMode.HEAT, + "Cool": HVACMode.COOL, + "Off": HVACMode.OFF, + "Fan only": HVACMode.FAN_ONLY, + "Dry": HVACMode.DRY, } CURRENT_HVAC_ACTIONS: Final = { - HVAC_MODE_HEAT: CURRENT_HVAC_HEAT, - HVAC_MODE_COOL: CURRENT_HVAC_COOL, - HVAC_MODE_OFF: CURRENT_HVAC_OFF, - HVAC_MODE_FAN_ONLY: CURRENT_HVAC_FAN, - HVAC_MODE_DRY: CURRENT_HVAC_DRY, + HVACMode.HEAT: HVACAction.HEATING, + HVACMode.COOL: HVACAction.COOLING, + HVACMode.OFF: HVACAction.OFF, + HVACMode.FAN_ONLY: HVACAction.FAN, + HVACMode.DRY: HVACAction.DRYING, } PRESET_MODES: Final = { diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 587f3d4f2c1..b3096a75df5 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -12,16 +12,9 @@ from homeassistant import config_entries from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.const import ( CONF_DEVICE_CLASS, @@ -84,20 +77,24 @@ class KNXCover(KnxEntity, CoverEntity): self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) _supports_tilt = False self._attr_supported_features = ( - SUPPORT_CLOSE | SUPPORT_OPEN | SUPPORT_SET_POSITION + CoverEntityFeature.CLOSE + | CoverEntityFeature.OPEN + | CoverEntityFeature.SET_POSITION ) if self._device.step.writable: _supports_tilt = True self._attr_supported_features |= ( - SUPPORT_CLOSE_TILT | SUPPORT_OPEN_TILT | SUPPORT_STOP_TILT + CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.STOP_TILT ) if self._device.supports_angle: _supports_tilt = True - self._attr_supported_features |= SUPPORT_SET_TILT_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_TILT_POSITION if self._device.supports_stop: - self._attr_supported_features |= SUPPORT_STOP + self._attr_supported_features |= CoverEntityFeature.STOP if _supports_tilt: - self._attr_supported_features |= SUPPORT_STOP_TILT + self._attr_supported_features |= CoverEntityFeature.STOP_TILT self._attr_device_class = config.get(CONF_DEVICE_CLASS) or ( CoverDeviceClass.BLIND if _supports_tilt else None @@ -163,10 +160,10 @@ class KNXCover(KnxEntity, CoverEntity): @property def current_cover_tilt_position(self) -> int | None: """Return current tilt position of cover.""" - if not self._device.supports_angle: - return None - ang = self._device.current_angle() - return 100 - ang if ang is not None else None + if self._device.supports_angle: + ang = self._device.current_angle() + return 100 - ang if ang is not None else None + return None async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index f5255fd25b0..27f9fb963f7 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -8,7 +8,7 @@ from xknx import XKNX from xknx.devices import Fan as XknxFan from homeassistant import config_entries -from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -78,10 +78,10 @@ class KNXFan(KnxEntity, FanEntity): @property def supported_features(self) -> int: """Flag supported features.""" - flags = SUPPORT_SET_SPEED + flags: int = FanEntityFeature.SET_SPEED if self._device.supports_oscillation: - flags |= SUPPORT_OSCILLATE + flags |= FanEntityFeature.OSCILLATE return flags diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index bd5ae199ccc..fff7f9b9f4f 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -45,5 +45,5 @@ class KnxEntity(Entity): async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" - # will also remove callbacks + # will also remove all callbacks self._device.shutdown() diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index c3e5f21cb3f..9268b53581b 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -14,13 +14,7 @@ from homeassistant.components.light import ( ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_XY_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_XY, + ColorMode, LightEntity, ) from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform @@ -267,24 +261,24 @@ class KNXLight(KnxEntity, LightEntity): return None @property - def color_mode(self) -> str | None: + def color_mode(self) -> ColorMode | None: """Return the color mode of the light.""" if self._device.supports_xyy_color: - return COLOR_MODE_XY + return ColorMode.XY if self._device.supports_hs_color: - return COLOR_MODE_HS + return ColorMode.HS if self._device.supports_rgbw: - return COLOR_MODE_RGBW + return ColorMode.RGBW if self._device.supports_color: - return COLOR_MODE_RGB + return ColorMode.RGB if ( self._device.supports_color_temperature or self._device.supports_tunable_white ): - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP if self._device.supports_brightness: - return COLOR_MODE_BRIGHTNESS - return COLOR_MODE_ONOFF + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF @property def supported_color_modes(self) -> set | None: @@ -373,17 +367,17 @@ class KNXLight(KnxEntity, LightEntity): await self._device.set_brightness(brightness) return # brightness without color in kwargs; set via color - if self.color_mode == COLOR_MODE_XY: + if self.color_mode == ColorMode.XY: await self._device.set_xyy_color(XYYColor(brightness=brightness)) return # default to white if color not known for RGB(W) - if self.color_mode == COLOR_MODE_RGBW: + if self.color_mode == ColorMode.RGBW: _rgbw = self.rgbw_color if not _rgbw or not any(_rgbw): _rgbw = (0, 0, 0, 255) await set_color(_rgbw[:3], _rgbw[3], brightness) return - if self.color_mode == COLOR_MODE_RGB: + if self.color_mode == ColorMode.RGB: _rgb = self.rgb_color if not _rgb or not any(_rgb): _rgb = (255, 255, 255) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 1058bf25ad8..00b4c6cdc5f 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -3,9 +3,9 @@ "name": "KNX", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.20.4"], + "requirements": ["xknx==0.21.2"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], - "quality_scale": "silver", + "quality_scale": "platinum", "iot_class": "local_push", "loggers": ["xknx"] } diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index ee170f55802..9d190ac78b0 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -7,7 +7,7 @@ from xknx import XKNX from xknx.devices import Notification as XknxNotification from homeassistant.components.notify import BaseNotificationService -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -34,6 +34,7 @@ async def async_get_service( xknx, name=device_config[CONF_NAME], group_address=device_config[KNX_ADDRESS], + value_type=device_config[CONF_TYPE], ) ) return ( diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 555fcfc575b..abba1b0c027 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -3,18 +3,20 @@ from __future__ import annotations from abc import ABC from collections import OrderedDict +from collections.abc import Callable +import ipaddress from typing import Any, ClassVar, Final import voluptuous as vol from xknx.devices.climate import SetpointShiftMode -from xknx.dpt import DPTBase, DPTNumeric +from xknx.dpt import DPTBase, DPTNumeric, DPTString from xknx.exceptions import ConversionError, CouldNotParseAddress from xknx.telegram.address import IndividualAddress, parse_device_group_address from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, ) -from homeassistant.components.climate.const import HVAC_MODE_HEAT, HVAC_MODES +from homeassistant.components.climate.const import HVACMode from homeassistant.components.cover import ( DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA, ) @@ -31,7 +33,7 @@ from homeassistant.const import ( Platform, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import validate_entity_category +from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA from .const import ( CONF_INVERT, @@ -53,6 +55,28 @@ from .const import ( ################## +def dpt_subclass_validator(dpt_base_class: type[DPTBase]) -> Callable[[Any], str | int]: + """Validate that value is parsable as given sensor type.""" + + def dpt_value_validator(value: Any) -> str | int: + """Validate that value is parsable as sensor type.""" + if ( + isinstance(value, (str, int)) + and dpt_base_class.parse_transcoder(value) is not None + ): + return value + raise vol.Invalid( + f"type '{value}' is not a valid DPT identifier for {dpt_base_class.__name__}." + ) + + return dpt_value_validator + + +numeric_type_validator = dpt_subclass_validator(DPTNumeric) # type: ignore[misc] +sensor_type_validator = dpt_subclass_validator(DPTBase) # type: ignore[misc] +string_type_validator = dpt_subclass_validator(DPTString) + + def ga_validator(value: Any) -> str | int: """Validate that value is parsable as GroupAddress or InternalGroupAddress.""" if isinstance(value, (str, int)): @@ -70,12 +94,29 @@ def ga_validator(value: Any) -> str | int: ga_list_validator = vol.All(cv.ensure_list, [ga_validator]) ia_validator = vol.Any( - cv.matches_regex(IndividualAddress.ADDRESS_RE.pattern), + vol.All(str, str.strip, cv.matches_regex(IndividualAddress.ADDRESS_RE.pattern)), vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), msg="value does not match pattern for KNX individual address '..' (eg.'1.1.100')", ) +def ip_v4_validator(value: Any, multicast: bool | None = None) -> str: + """ + Validate that value is parsable as IPv4 address. + + Optionally check if address is in a reserved multicast block or is explicitly not. + """ + try: + address = ipaddress.IPv4Address(value) + except ipaddress.AddressValueError as ex: + raise vol.Invalid(f"value '{value}' is not a valid IPv4 address: {ex}") from ex + if multicast is not None and address.is_multicast != multicast: + raise vol.Invalid( + f"value '{value}' is not a valid IPv4 {'multicast' if multicast else 'unicast'} address" + ) + return str(address) + + def number_limit_sub_validator(entity_config: OrderedDict) -> OrderedDict: """Validate a number entity configurations dependent on configured value type.""" value_type = entity_config[CONF_TYPE] @@ -113,13 +154,6 @@ def number_limit_sub_validator(entity_config: OrderedDict) -> OrderedDict: return entity_config -def numeric_type_validator(value: Any) -> str | int: - """Validate that value is parsable as numeric sensor type.""" - if isinstance(value, (str, int)) and DPTNumeric.parse_transcoder(value) is not None: - return value - raise vol.Invalid(f"value '{value}' is not a valid numeric sensor type.") - - def _max_payload_value(payload_length: int) -> int: if payload_length == 0: return 0x3F @@ -176,13 +210,6 @@ def select_options_sub_validator(entity_config: OrderedDict) -> OrderedDict: return entity_config -def sensor_type_validator(value: Any) -> str | int: - """Validate that value is parsable as sensor type.""" - if isinstance(value, (str, int)) and DPTBase.parse_transcoder(value) is not None: - return value - raise vol.Invalid(f"value '{value}' is not a valid sensor type.") - - sync_state_validator = vol.Any( vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), cv.boolean, @@ -262,7 +289,7 @@ class BinarySensorSchema(KNXPlatformSchema): ), vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_RESET_AFTER): cv.positive_float, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), ) @@ -298,7 +325,7 @@ class ButtonSchema(KNXPlatformSchema): vol.Exclusive( CONF_TYPE, "length_or_type", msg=length_or_type_msg ): object, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), vol.Any( @@ -438,11 +465,11 @@ class ClimateSchema(KNXPlatformSchema): cv.ensure_list, [vol.In(CONTROLLER_MODES)] ), vol.Optional( - CONF_DEFAULT_CONTROLLER_MODE, default=HVAC_MODE_HEAT - ): vol.In(HVAC_MODES), + CONF_DEFAULT_CONTROLLER_MODE, default=HVACMode.HEAT + ): vol.Coerce(HVACMode), vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), ) @@ -497,7 +524,7 @@ class CoverSchema(KNXPlatformSchema): vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), ) @@ -560,7 +587,7 @@ class FanSchema(KNXPlatformSchema): vol.Optional(CONF_OSCILLATION_ADDRESS): ga_list_validator, vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_list_validator, vol.Optional(CONF_MAX_STEP): cv.byte, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ) @@ -664,7 +691,7 @@ class LightSchema(KNXPlatformSchema): vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All( vol.Coerce(int), vol.Range(min=1) ), - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), vol.Any( @@ -715,6 +742,7 @@ class NotifySchema(KNXPlatformSchema): ENTITY_SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TYPE, default="latin_1"): string_type_validator, vol.Required(KNX_ADDRESS): ga_validator, } ) @@ -744,7 +772,7 @@ class NumberSchema(KNXPlatformSchema): vol.Optional(CONF_MAX): vol.Coerce(float), vol.Optional(CONF_MIN): vol.Coerce(float), vol.Optional(CONF_STEP): cv.positive_float, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), number_limit_sub_validator, @@ -766,7 +794,7 @@ class SceneSchema(KNXPlatformSchema): vol.Required(CONF_SCENE_NUMBER): vol.All( vol.Coerce(int), vol.Range(min=1, max=64) ), - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ) @@ -797,7 +825,7 @@ class SelectSchema(KNXPlatformSchema): ], vol.Required(KNX_ADDRESS): ga_list_validator, vol.Optional(CONF_STATE_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), select_options_sub_validator, @@ -822,7 +850,7 @@ class SensorSchema(KNXPlatformSchema): vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA, vol.Required(CONF_TYPE): sensor_type_validator, vol.Required(CONF_STATE_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ) @@ -843,7 +871,7 @@ class SwitchSchema(KNXPlatformSchema): vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean, vol.Required(KNX_ADDRESS): ga_list_validator, vol.Optional(CONF_STATE_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ) @@ -890,7 +918,7 @@ class WeatherSchema(KNXPlatformSchema): vol.Optional(CONF_KNX_DAY_NIGHT_ADDRESS): ga_list_validator, vol.Optional(CONF_KNX_AIR_PRESSURE_ADDRESS): ga_list_validator, vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): ga_list_validator, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, } ), ) diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index 2149dd96a47..018db071adf 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -62,8 +62,8 @@ "description": "Please configure the routing options.", "data": { "individual_address": "Individual address", - "multicast_group": "Multicast group used for routing", - "multicast_port": "Multicast port used for routing", + "multicast_group": "Multicast group", + "multicast_port": "Multicast port", "local_ip": "Local IP of Home Assistant" }, "data_description": { @@ -78,8 +78,10 @@ }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_signature": "The password to decrypt the knxkeys file is wrong.", - "file_not_found": "The specified knxkeys file was not found in the path config/.storage/knx/" + "invalid_individual_address": "Value does not match pattern for KNX individual address.\n'area.line.device'", + "invalid_ip_address": "Invalid IPv4 address.", + "invalid_signature": "The password to decrypt the `.knxkeys` file is wrong.", + "file_not_found": "The specified `.knxkeys` file was not found in the path config/.storage/knx/" } }, "options": { @@ -88,8 +90,8 @@ "data": { "connection_type": "KNX Connection Type", "individual_address": "Default individual address", - "multicast_group": "Multicast group", - "multicast_port": "Multicast port", + "multicast_group": "[%key:component::knx::config::step::routing::data::multicast_group%]", + "multicast_port": "[%key:component::knx::config::step::routing::data::multicast_port%]", "local_ip": "Local IP of Home Assistant", "state_updater": "State updater", "rate_limit": "Rate limit" @@ -110,8 +112,8 @@ "host": "[%key:common::config_flow::data::host%]" }, "data_description": { - "port": "Port of the KNX/IP tunneling device.", - "host": "IP address of the KNX/IP tunneling device." + "port": "[%key:component::knx::config::step::manual_tunnel::data_description::port%]", + "host": "[%key:component::knx::config::step::manual_tunnel::data_description::host%]" } } } diff --git a/homeassistant/components/knx/translations/bg.json b/homeassistant/components/knx/translations/bg.json index 43f72f49867..3cbc8b57b0b 100644 --- a/homeassistant/components/knx/translations/bg.json +++ b/homeassistant/components/knx/translations/bg.json @@ -5,7 +5,8 @@ "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." }, "error": { - "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_ip_address": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d IPv4 \u0430\u0434\u0440\u0435\u0441." }, "step": { "manual_tunnel": { @@ -14,11 +15,23 @@ "local_ip": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d IP (\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u043d\u0435 \u0441\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0438)", "port": "\u041f\u043e\u0440\u0442", "tunneling_type": "KNX \u0442\u0443\u043d\u0435\u043b\u0435\u043d \u0442\u0438\u043f" + }, + "data_description": { + "local_ip": "\u041e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0437\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435." } }, "routing": { "data": { "local_ip": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d IP \u0430\u0434\u0440\u0435\u0441 (\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u043d\u0435 \u0441\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0438)" + }, + "data_description": { + "local_ip": "\u041e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0437\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435." + } + }, + "secure_manual": { + "data": { + "user_id": "\u0418\u0414 \u043d\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f", + "user_password": "\u041f\u0430\u0440\u043e\u043b\u0430 \u043d\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f" } } } @@ -28,6 +41,9 @@ "init": { "data": { "local_ip": "\u041b\u043e\u043a\u0430\u043b\u0435\u043d IP \u0430\u0434\u0440\u0435\u0441 (\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, \u0430\u043a\u043e \u043d\u0435 \u0441\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0438)" + }, + "data_description": { + "local_ip": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 `0.0.0.0` \u0437\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435." } }, "tunnel": { diff --git a/homeassistant/components/knx/translations/ca.json b/homeassistant/components/knx/translations/ca.json index dd553f293be..b79887e1586 100644 --- a/homeassistant/components/knx/translations/ca.json +++ b/homeassistant/components/knx/translations/ca.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { - "cannot_connect": "Ha fallat la connexi\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3", + "file_not_found": "No s'ha trobat el fitxer `.knxkeys` especificat a la ruta config/.storage/knx/", + "invalid_individual_address": "El valor no coincideix amb el patr\u00f3 d'adre\u00e7a KNX individual.\n'area.line.device'", + "invalid_ip_address": "Adre\u00e7a IPv4 inv\u00e0lida.", + "invalid_signature": "La contrasenya per desxifrar el fitxer `.knxkeys` \u00e9s incorrecta." }, "step": { "manual_tunnel": { "data": { "host": "Amfitri\u00f3", "individual_address": "Adre\u00e7a individual de la connexi\u00f3", - "local_ip": "IP local de Home Assistant (deixa-ho en blanc si no n'est\u00e0s segur/a)", + "local_ip": "IP local de Home Assistant", "port": "Port", "route_back": "Encaminament de retorn / Mode NAT", "tunneling_type": "Tipus de t\u00fanel KNX" }, + "data_description": { + "host": "Adre\u00e7a IP del dispositiu de tunelitzaci\u00f3 KNX/IP.", + "local_ip": "Deixa-ho en blanc per utilitzar el descobriment autom\u00e0tic.", + "port": "Port del dispositiu de tunelitzaci\u00f3 KNX/IP." + }, "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del dispositiu de t\u00fanel." }, "routing": { "data": { - "individual_address": "Adre\u00e7a individual de la connexi\u00f3 d'encaminament", - "local_ip": "IP local de Home Assistant (deixa-ho en blanc si no n'est\u00e0s segur/a)", - "multicast_group": "Grup de multidifusi\u00f3 utilitzat per a l'encaminament", - "multicast_port": "Port de multidifusi\u00f3 utilitzat per a l'encaminament" + "individual_address": "Adre\u00e7a individual", + "local_ip": "IP local de Home Assistant", + "multicast_group": "Grup multidifusi\u00f3", + "multicast_port": "Port multidifusi\u00f3" + }, + "data_description": { + "individual_address": "Adre\u00e7a KNX per utilitzar amb Home Assistant, p. ex. `0.0.4`", + "local_ip": "Deixa-ho en blanc per utilitzar el descobriment autom\u00e0tic." }, "description": "Configura les opcions d'encaminament." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Nom del teu fitxer `.knxkeys` (inclosa l'extensi\u00f3)", + "knxkeys_password": "Contrasenya per desxifrar el fitxer `.knxkeys`." + }, + "data_description": { + "knxkeys_filename": "S'espera que el fitxer es trobi al teu directori de configuraci\u00f3 a `.storage/knx/`.\nA Home Assistant aix\u00f2 estaria a `/config/.storage/knx/`\nExemple: `el_meu_projecte.knxkeys`", + "knxkeys_password": "S'ha definit durant l'exportaci\u00f3 del fitxer des d'ETS." + }, + "description": "Introdueix la informaci\u00f3 del teu fitxer `.knxkeys`." + }, + "secure_manual": { + "data": { + "device_authentication": "Contrasenya d'autenticaci\u00f3 del dispositiu", + "user_id": "ID d'usuari", + "user_password": "Contrasenya d'usuari" + }, + "data_description": { + "device_authentication": "S'estableix al panell 'IP' de la interf\u00edcie d'ETS.", + "user_id": "Sovint \u00e9s el n\u00famero del t\u00fanel +1. Per tant, 'T\u00fanel 2' tindria l'ID d'usuari '3'.", + "user_password": "Contrasenya per a la connexi\u00f3 t\u00fanel espec\u00edfica configurada al panell 'Propietats' del t\u00fanel a ETS." + }, + "description": "Introdueix la informaci\u00f3 de seguretat IP (IP Secure)." + }, + "secure_tunneling": { + "description": "Selecciona com vols configurar KNX/IP Secure.", + "menu_options": { + "secure_knxkeys": "Utilitza un fitxer `.knxkeys` que contingui les claus de seguretat IP (IP Secure)", + "secure_manual": "Configura manualment les claus de seguretat IP (IP Secure)" + } + }, "tunnel": { "data": { "gateway": "Connexi\u00f3 t\u00fanel KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "Tipus de connexi\u00f3 KNX", "individual_address": "Adre\u00e7a individual predeterminada", - "local_ip": "IP local de Home Assistant (utilitza 0.0.0.0 per a detecci\u00f3 autom\u00e0tica)", - "multicast_group": "Grup de multidifusi\u00f3 utilitzat per a encaminament i descobriment", - "multicast_port": "Port de multidifusi\u00f3 utilitzat per a encaminament i descobriment", - "rate_limit": "Telegrames de sortida m\u00e0xims per segon", - "state_updater": "Habilita la lectura global d'estats del bus KNX" + "local_ip": "IP local de Home Assistant", + "multicast_group": "Grup multidifusi\u00f3", + "multicast_port": "Port multidifusi\u00f3", + "rate_limit": "Freq\u00fc\u00e8ncia m\u00e0xima", + "state_updater": "Actualitzador d'estat" + }, + "data_description": { + "individual_address": "Adre\u00e7a KNX per utilitzar amb Home Assistant, p. ex. `0.0.4`", + "local_ip": "Utilitza `0.0.0.0` per al descobriment autom\u00e0tic.", + "multicast_group": "Utilitzada per a l'encaminament i el descobriment. Per defecte: `224.0.23.12`", + "multicast_port": "Utilitzat per a l'encaminament i el descobriment. Per defecte: `3671`", + "rate_limit": "Telegrames de sortida m\u00e0xims per segon.\nRecomanat: de 20 a 40", + "state_updater": "Activa o desactiva globalment la lectura d'estats del bus KNX. Si est\u00e0 desactivat, Home Assistant no obtindr\u00e0 activament els estats del bus KNX, les opcions d'entitat `sync_state` no tindran cap efecte." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Encaminament de retorn / Mode NAT", "tunneling_type": "Tipus de t\u00fanel KNX" + }, + "data_description": { + "host": "Adre\u00e7a IP del dispositiu de tunelitzaci\u00f3 KNX/IP.", + "port": "Port del dispositiu de tunelitzaci\u00f3 KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/de.json b/homeassistant/components/knx/translations/de.json index 2f7795153dc..588a853a8b6 100644 --- a/homeassistant/components/knx/translations/de.json +++ b/homeassistant/components/knx/translations/de.json @@ -5,34 +5,78 @@ "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "file_not_found": "Die angegebene `.knxkeys`-Datei wurde im Pfad config/.storage/knx/ nicht gefunden.", + "invalid_individual_address": "Wert ist keine g\u00fcltige physikalische Adresse. 'Bereich.Linie.Teilnehmer'", + "invalid_ip_address": "Ung\u00fcltige IPv4 Adresse.", + "invalid_signature": "Das Passwort zum Entschl\u00fcsseln der `.knxkeys`-Datei ist ung\u00fcltig." }, "step": { "manual_tunnel": { "data": { "host": "Host", "individual_address": "Physikalische Adresse f\u00fcr die Verbindung", - "local_ip": "Lokale IP von Home Assistant (f\u00fcr automatische Erkennung leer lassen)", + "local_ip": "Lokale IP von Home Assistant", "port": "Port", "route_back": "Route Back / NAT-Modus", "tunneling_type": "KNX Tunneling Typ" }, - "description": "Bitte gib die Verbindungsinformationen deines Tunnelger\u00e4ts ein." + "data_description": { + "host": "IP-Adresse der KNX/IP-Tunneling Schnittstelle.", + "local_ip": "Lasse das Feld leer, um die automatische Erkennung zu verwenden.", + "port": "Port der KNX/IP-Tunneling Schnittstelle." + }, + "description": "Bitte gib die Verbindungsinformationen deiner Tunnel-Schnittstelle ein." }, "routing": { "data": { - "individual_address": "Physikalische Adresse f\u00fcr die Routingverbindung", - "local_ip": "Lokale IP von Home Assistant (f\u00fcr automatische Erkennung leer lassen)", - "multicast_group": "Die f\u00fcr das Routing verwendete Multicast-Gruppe", - "multicast_port": "Der f\u00fcr das Routing verwendete Multicast-Port" + "individual_address": "Physikalische Adresse", + "local_ip": "Lokale IP von Home Assistant", + "multicast_group": "Multicast-Gruppe", + "multicast_port": "Multicast-Port" + }, + "data_description": { + "individual_address": "Physikalische Adresse, die von Home Assistant verwendet werden soll, z. B. \u201e0.0.4\u201c.", + "local_ip": "Lasse das Feld leer, um die automatische Erkennung zu verwenden." }, "description": "Bitte konfiguriere die Routing-Optionen." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Der Dateiname deiner `.knxkeys`-Datei (einschlie\u00dflich Erweiterung)", + "knxkeys_password": "Das Passwort zum Entschl\u00fcsseln der `.knxkeys`-Datei" + }, + "data_description": { + "knxkeys_filename": "Die Datei wird in deinem Konfigurationsverzeichnis unter `.storage/knx/` erwartet.\nIm Home Assistant OS w\u00e4re dies `/config/.storage/knx/`\nBeispiel: `my_project.knxkeys`", + "knxkeys_password": "Dies wurde beim Exportieren der Datei aus ETS gesetzt." + }, + "description": "Bitte gib die Informationen f\u00fcr deine `.knxkeys`-Datei ein." + }, + "secure_manual": { + "data": { + "device_authentication": "Ger\u00e4te-Authentifizierungscode", + "user_id": "Benutzer-ID", + "user_password": "Benutzer-Passwort" + }, + "data_description": { + "device_authentication": "Dies wird im Feld \"IP\" der Schnittstelle in ETS eingestellt.", + "user_id": "Dies ist oft die Tunnelnummer +1. \u201eTunnel 2\u201c h\u00e4tte also die Benutzer-ID \u201e3\u201c.", + "user_password": "Passwort f\u00fcr die spezifische Tunnelverbindung, die im Bereich \u201eEigenschaften\u201c des Tunnels in ETS festgelegt wurde." + }, + "description": "Bitte gib deine IP-Secure Informationen ein." + }, + "secure_tunneling": { + "description": "W\u00e4hle aus, wie du KNX/IP-Secure konfigurieren m\u00f6chtest.", + "menu_options": { + "secure_knxkeys": "Verwende eine `.knxkeys`-Datei, die IP-Secure-Schl\u00fcssel enth\u00e4lt", + "secure_manual": "IP-Secure Schl\u00fcssel manuell konfigurieren" + } + }, "tunnel": { "data": { "gateway": "KNX Tunnel Verbindung" }, - "description": "Bitte w\u00e4hle ein Gateway aus der Liste aus." + "description": "Bitte w\u00e4hle eine Schnittstelle aus der Liste aus." }, "type": { "data": { @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX-Verbindungstyp", "individual_address": "Standard physikalische Adresse", - "local_ip": "Lokale IP von Home Assistant (verwende 0.0.0.0 f\u00fcr automatische Erkennung)", - "multicast_group": "Multicast-Gruppe f\u00fcr Routing und Discovery", - "multicast_port": "Multicast-Port f\u00fcr Routing und Discovery", - "rate_limit": "Maximal ausgehende Telegramme pro Sekunde", - "state_updater": "Lesen von Zust\u00e4nden von dem KNX Bus global freigeben" + "local_ip": "Lokale IP von Home Assistant", + "multicast_group": "Multicast-Gruppe", + "multicast_port": "Multicast-Port", + "rate_limit": "Telegrammdrossel", + "state_updater": "Status-Updater" + }, + "data_description": { + "individual_address": "Physikalische Adresse, die von Home Assistant verwendet werden soll, z.\u00a0B. \u201e0.0.4\u201c.", + "local_ip": "Verwende \"0.0.0.0\" f\u00fcr die automatische Erkennung.", + "multicast_group": "Wird f\u00fcr Routing und Netzwerkerkennung verwendet. Standard: `224.0.23.12`", + "multicast_port": "Wird f\u00fcr Routing und Netzwerkerkennung verwendet. Standard: \u201e3671\u201c.", + "rate_limit": "Maximal gesendete Telegramme pro Sekunde.\nEmpfohlen: 20 bis 40", + "state_updater": "Aktiviere oder deaktiviere global das Lesen von Zust\u00e4nden vom KNX-Bus. Wenn deaktiviert, ruft Home Assistant nicht aktiv Zust\u00e4nde vom KNX-Bus ab, \u201esync_state\u201c-Entity-Optionen haben keine Auswirkung." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Route Back / NAT-Modus", "tunneling_type": "KNX Tunneling Typ" + }, + "data_description": { + "host": "IP-Adresse der KNX/IP-Tunneling Schnittstelle.", + "port": "Port der KNX/IP-Tunneling Schnittstelle." } } } diff --git a/homeassistant/components/knx/translations/el.json b/homeassistant/components/knx/translations/el.json index 75be33560ca..81dd03f73fa 100644 --- a/homeassistant/components/knx/translations/el.json +++ b/homeassistant/components/knx/translations/el.json @@ -5,7 +5,11 @@ "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." }, "error": { - "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "file_not_found": "\u03a4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf knxkeys \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae config/.storage/knx/", + "invalid_individual_address": "\u0397 \u03c4\u03b9\u03bc\u03ae \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf \u03bc\u03bf\u03c4\u03af\u03b2\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03bc\u03b5\u03bc\u03bf\u03bd\u03c9\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 KNX.\n \"area.line.device\"", + "invalid_ip_address": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IPv4.", + "invalid_signature": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03ba\u03c1\u03c5\u03c0\u03c4\u03bf\u03b3\u03c1\u03ac\u03c6\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 knxkeys \u03b5\u03af\u03bd\u03b1\u03b9 \u03bb\u03ac\u03b8\u03bf\u03c2." }, "step": { "manual_tunnel": { @@ -17,6 +21,11 @@ "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" }, + "data_description": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b9\u03bf\u03c7\u03ad\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 KNX/IP.", + "local_ip": "\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7.", + "port": "\u0398\u03cd\u03c1\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b9\u03bf\u03c7\u03ad\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 KNX/IP." + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03ac\u03c2 \u03c3\u03b1\u03c2." }, "routing": { @@ -26,8 +35,43 @@ "multicast_group": "\u0397 \u03bf\u03bc\u03ac\u03b4\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b5\u03ba\u03c0\u03bf\u03bc\u03c0\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7", "multicast_port": "\u0397 \u03b8\u03cd\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7" }, + "data_description": { + "individual_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 KNX \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf Home Assistant, \u03c0.\u03c7. `0.0.4`.", + "local_ip": "\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7." + }, "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7\u03c2." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "\u03a4\u03bf \u03c0\u03bb\u03ae\u03c1\u03b5\u03c2 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 knxkeys \u03c3\u03b1\u03c2", + "knxkeys_password": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03ba\u03c1\u03c5\u03c0\u03c4\u03bf\u03b3\u03c1\u03ac\u03c6\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 knxkeys" + }, + "data_description": { + "knxkeys_filename": "\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b1\u03bd\u03b1\u03bc\u03ad\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03bf\u03bd \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd \u03c3\u03c4\u03bf `.storage/knx/`.\n \u03a3\u03c4\u03bf Home Assistant OS \u03b1\u03c5\u03c4\u03cc \u03b8\u03b1 \u03ae\u03c4\u03b1\u03bd `/config/.storage/knx/`\n \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1: `my_project.knxkeys`", + "knxkeys_password": "\u0391\u03c5\u03c4\u03cc \u03bf\u03c1\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03be\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5 \u03b1\u03c0\u03cc \u03c4\u03bf ETS." + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf knxkeys \u03c3\u03b1\u03c2." + }, + "secure_manual": { + "data": { + "device_authentication": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "user_id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "user_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "data_description": { + "device_authentication": "\u0391\u03c5\u03c4\u03cc \u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u00abIP\u00bb \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae\u03c2 \u03c3\u03c4\u03bf ETS.", + "user_id": "\u0391\u03c5\u03c4\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03c7\u03bd\u03ac \u03c4\u03bf \u03bd\u03bf\u03cd\u03bc\u03b5\u03c1\u03bf +1 \u03c4\u03b7\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2. \u0388\u03c4\u03c3\u03b9, \u03b7 '\u03a3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1 2' \u03b8\u03b1 \u03ad\u03c7\u03b5\u03b9 User-ID '3'.", + "user_password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03b3\u03ba\u03b5\u03ba\u03c1\u03b9\u03bc\u03ad\u03bd\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03ad\u03c7\u03b5\u03b9 \u03bf\u03c1\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c4\u03bf\u03bd \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \"\u0399\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2\" \u03c4\u03b7\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 \u03c3\u03c4\u03bf ETS." + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 IP secure." + }, + "secure_tunneling": { + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c0\u03ce\u03c2 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf IP Secure.", + "menu_options": { + "secure_knxkeys": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf knxkeys \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03ad\u03c7\u03b5\u03b9 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 IP secure", + "secure_manual": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 IP secure \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1" + } + }, "tunnel": { "data": { "gateway": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" @@ -47,12 +91,20 @@ "init": { "data": { "connection_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 KNX", - "individual_address": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c4\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7", + "individual_address": "\u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03c6\u03c5\u03c3\u03b9\u03ba\u03ae \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7", "local_ip": "\u03a4\u03bf\u03c0\u03b9\u03ba\u03ae IP \u03c4\u03bf\u03c5 Home Assistant (\u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 0.0.0.0.0 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7)", "multicast_group": "\u039f\u03bc\u03ac\u03b4\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", "multicast_port": "\u0398\u03cd\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ae\u03c2 \u03b4\u03b9\u03b1\u03bd\u03bf\u03bc\u03ae\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7", "rate_limit": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b1 \u03b5\u03be\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b7\u03bb\u03b5\u03b3\u03c1\u03b1\u03c6\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b1\u03bd\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03bf", "state_updater": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b1\u03b8\u03bf\u03bb\u03b9\u03ba\u03ac \u03c4\u03b9\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf KNX Bus" + }, + "data_description": { + "individual_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 KNX \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf Home Assistant, \u03c0.\u03c7. `0.0.4`.", + "local_ip": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 `0.0.0.0.0` \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7.", + "multicast_group": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7. \u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae: `224.0.23.12`", + "multicast_port": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7. \u03a0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae: `3671`", + "rate_limit": "\u039c\u03ad\u03b3\u03b9\u03c3\u03c4\u03b1 \u03b5\u03be\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b7\u03bb\u03b5\u03b3\u03c1\u03b1\u03c6\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b1\u03bd\u03ac \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03bf.\n \u03a0\u03c1\u03bf\u03c4\u03b5\u03af\u03bd\u03b5\u03c4\u03b1\u03b9: 20 \u03ad\u03c9\u03c2 40", + "state_updater": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ae \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03c9\u03bd \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b4\u03af\u03b1\u03c5\u03bb\u03bf KNX. \u038c\u03c4\u03b1\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf, \u03c4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03b8\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03ac \u03b5\u03bd\u03b5\u03c1\u03b3\u03ac \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf KNX Bus, \u03bf\u03b9 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 `sync_state` \u03b4\u03b5\u03bd \u03b8\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd \u03ba\u03b1\u03bc\u03af\u03b1 \u03b5\u03c0\u03af\u03b4\u03c1\u03b1\u03c3\u03b7." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "\u0398\u03cd\u03c1\u03b1", "route_back": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 Route Back / NAT", "tunneling_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03ae\u03c1\u03b1\u03b3\u03b3\u03b1\u03c2 KNX" + }, + "data_description": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b9\u03bf\u03c7\u03ad\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 KNX/IP.", + "port": "\u0398\u03cd\u03c1\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03b4\u03b9\u03bf\u03c7\u03ad\u03c4\u03b5\u03c5\u03c3\u03b7\u03c2 KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index 640cb4a5358..ba073eba888 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -6,15 +6,19 @@ }, "error": { "cannot_connect": "Failed to connect", - "file_not_found": "The specified knxkeys file was not found in the path config/.storage/knx/", - "invalid_signature": "The password to decrypt the knxkeys file is wrong." + "file_not_found": "The specified `.knxkeys` file was not found in the path config/.storage/knx/", + "invalid_individual_address": "Value does not match pattern for KNX individual address.\n'area.line.device'", + "invalid_ip_address": "Invalid IPv4 address.", + "invalid_signature": "The password to decrypt the `.knxkeys` file is wrong." }, "step": { "manual_tunnel": { "data": { "host": "Host", + "individual_address": "Individual address for the connection", "local_ip": "Local IP of Home Assistant", "port": "Port", + "route_back": "Route Back / NAT Mode", "tunneling_type": "KNX Tunneling Type" }, "data_description": { @@ -28,8 +32,8 @@ "data": { "individual_address": "Individual address", "local_ip": "Local IP of Home Assistant", - "multicast_group": "Multicast group used for routing", - "multicast_port": "Multicast port used for routing" + "multicast_group": "Multicast group", + "multicast_port": "Multicast port" }, "data_description": { "individual_address": "KNX address to be used by Home Assistant, e.g. `0.0.4`", @@ -106,7 +110,9 @@ "tunnel": { "data": { "host": "Host", + "local_ip": "Local IP (leave empty if unsure)", "port": "Port", + "route_back": "Route Back / NAT Mode", "tunneling_type": "KNX Tunneling Type" }, "data_description": { diff --git a/homeassistant/components/knx/translations/et.json b/homeassistant/components/knx/translations/et.json index e9417cdac37..c4410d490e4 100644 --- a/homeassistant/components/knx/translations/et.json +++ b/homeassistant/components/knx/translations/et.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Juba seadistatud. Lubatud on ainult \u00fcks sidumine." }, "error": { - "cannot_connect": "\u00dchendamine nurjus" + "cannot_connect": "\u00dchendamine nurjus", + "file_not_found": "M\u00e4\u00e4ratud faili \".knxkeys\" ei leitud asukohas config/.storage/knx/", + "invalid_individual_address": "V\u00e4\u00e4rtus ei \u00fchti KNX-i individuaalse aadressi mustriga.\n 'area.line.device'", + "invalid_ip_address": "Kehtetu IPv4 aadress.", + "invalid_signature": "Parool faili `.knxkeys` dekr\u00fcpteerimiseks on vale." }, "step": { "manual_tunnel": { "data": { "host": "Host", "individual_address": "\u00dchenduse individuaalne aadress", - "local_ip": "Home Assistanti kohalik IP (automaatseks tuvastuseks j\u00e4ta t\u00fchjaks)", + "local_ip": "Home Assistanti kohalik IP aadress", "port": "Port", "route_back": "Marsruudi tagasitee / NAT-re\u017eiim", "tunneling_type": "KNX tunneli t\u00fc\u00fcp" }, + "data_description": { + "host": "KNX/IP tunneldusseadme IP-aadress.", + "local_ip": "Automaatse avastamise kasutamiseks j\u00e4ta t\u00fchjaks.", + "port": "KNX/IP-tunneldusseadme port." + }, "description": "Sisesta tunneldamisseadme \u00fchenduse teave." }, "routing": { "data": { - "individual_address": "Marsruutimis\u00fchenduse individuaalne aadress", - "local_ip": "Home Assistanti kohalik IP (automaatseks tuvastuseks j\u00e4ta t\u00fchjaks)", - "multicast_group": "Marsruutimiseks kasutatav multisaater\u00fchm", - "multicast_port": "Marsruutimiseks kasutatav multisaateport" + "individual_address": "Individuaalne aadress", + "local_ip": "Home Assistanti kohalik IP aadress", + "multicast_group": "Multicast grupp", + "multicast_port": "Mulicasti port" + }, + "data_description": { + "individual_address": "Home Assistantis kasutatav KNX-aadress, nt \"0.0.4\".", + "local_ip": "Automaatse avastamise kasutamiseks j\u00e4ta t\u00fchjaks." }, "description": "Konfigureeri marsruutimissuvandid." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "`.nxkeys` faili t\u00e4ielik nimi (koos laiendiga)", + "knxkeys_password": "Parool `.knxkeys` faili dekr\u00fcpteerimiseks" + }, + "data_description": { + "knxkeys_filename": "Eeldatakse, et fail asub konfiguratsioonikataloogis kaustas \".storage/knx/\".\nHome Assistant OS-is oleks see `/config/.storage/knx/`\n N\u00e4ide: \"minu_projekt.knxkeys\".", + "knxkeys_password": "See m\u00e4\u00e4rati faili eksportimisel ETSist." + }, + "description": "Sisesta oma `.knxkeys` faili teave." + }, + "secure_manual": { + "data": { + "device_authentication": "Seadme autentimise parool", + "user_id": "Kasutaja ID", + "user_password": "Kasutaja salas\u00f5na" + }, + "data_description": { + "device_authentication": "See m\u00e4\u00e4ratakse ETSi liidese IP-paneelil.", + "user_id": "See on sageli tunneli number +1. Nii et tunnel 2 oleks kasutaja ID-ga 3.", + "user_password": "Konkreetse tunneli\u00fchenduse parool, mis on m\u00e4\u00e4ratud ETS-i tunneli paneelil \u201eAtribuudid\u201d." + }, + "description": "Sisesta IP Secure teave." + }, + "secure_tunneling": { + "description": "Vali kuidas soovid KNX/IP Secure'i seadistada.", + "menu_options": { + "secure_knxkeys": "Kasuta knxkeys fail, mis sisaldab IP Secure teavet.", + "secure_manual": "IP Secure v\u00f5tmete k\u00e4sitsi seadistamine" + } + }, "tunnel": { "data": { "gateway": "KNX tunneli \u00fchendus" @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX \u00fchenduse t\u00fc\u00fcp", "individual_address": "Vaikimisi individuaalne aadress", - "local_ip": "Home Assistanti kohalik IP (sisesta 0.0.0.0 automaatseks tuvastuseks)", - "multicast_group": "Marsruutimiseks ja avastamiseks kasutatav multisaategrupp", - "multicast_port": "Marsruutimiseks ja avastamiseks kasutatav multisaateport", - "rate_limit": "Maksimaalne v\u00e4ljaminevate teavituste arv sekundis", - "state_updater": "Luba globaalselt seisundi lugemine KNX-siinilt" + "local_ip": "Home Assistanti kohalik IP aadress", + "multicast_group": "Multicast grupp", + "multicast_port": "Mulicasti port", + "rate_limit": "Teavituste m\u00e4\u00e4r", + "state_updater": "Oleku uuendaja" + }, + "data_description": { + "individual_address": "Home Assistantis kasutatav KNX-aadress, nt \"0.0.4\".", + "local_ip": "Automaatse tuvastamise jaoks kasuta `0.0.0.0.0`.", + "multicast_group": "Kasutatakse marsruutimiseks ja avastamiseks. Vaikimisi: \"224.0.23.12\"", + "multicast_port": "Kasutatakse marsruutimiseks ja avastamiseks. Vaikev\u00e4\u00e4rtus: \"3671\"", + "rate_limit": "Maksimaalne v\u00e4ljaminevate telegrammide arv sekundis.\nSoovitatav: 20 kuni 40", + "state_updater": "KNX siini lugemisolekute globaalne lubamine v\u00f5i keelamine. Kui see on keelatud, ei too Home Assistant aktiivselt olekuid KNX siinilt, olemi s\u00fcnkroonimisoleku valikudi ei m\u00f5juta." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Marsruudi tagasitee / NAT-re\u017eiim", "tunneling_type": "KNX tunneli t\u00fc\u00fcp" + }, + "data_description": { + "host": "KNX/IP tunneldusseadme IP-aadress.", + "port": "KNX/IP-tunneldusseadme port." } } } diff --git a/homeassistant/components/knx/translations/fr.json b/homeassistant/components/knx/translations/fr.json index 31221e900cd..c75fd76debb 100644 --- a/homeassistant/components/knx/translations/fr.json +++ b/homeassistant/components/knx/translations/fr.json @@ -5,29 +5,73 @@ "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "error": { - "cannot_connect": "\u00c9chec de connexion" + "cannot_connect": "\u00c9chec de connexion", + "file_not_found": "Le fichier `.knxkeys` sp\u00e9cifi\u00e9 n'a pas \u00e9t\u00e9 trouv\u00e9 dans config/.storage/knx/", + "invalid_individual_address": "La valeur de l'adresse individuelle KNX ne correspond pas au mod\u00e8le.\n'area.line.device'", + "invalid_ip_address": "Adresse IPv4 non valide.", + "invalid_signature": "Le mot de passe pour d\u00e9chiffrer le fichier `.knxkeys` est erron\u00e9." }, "step": { "manual_tunnel": { "data": { "host": "H\u00f4te", "individual_address": "Adresse individuelle pour la connexion", - "local_ip": "IP locale (laisser vide en cas de doute)", + "local_ip": "IP locale de Home Assistant", "port": "Port", "route_back": "Retour/Mode NAT", "tunneling_type": "Type de tunnel KNX" }, + "data_description": { + "host": "Adresse IP de l'appareil de tunnel KNX/IP.", + "local_ip": "Laissez le champ vide pour utiliser la d\u00e9couverte automatique.", + "port": "Port de l'appareil de tunnel KNX/IP." + }, "description": "Veuillez saisir les informations de connexion de votre appareil de cr\u00e9ation de tunnel." }, "routing": { "data": { - "individual_address": "Adresse individuelle pour la connexion de routage", - "local_ip": "IP locale (laisser vide en cas de doute)", - "multicast_group": "Le groupe multicast utilis\u00e9 pour le routage", - "multicast_port": "Le port multicast utilis\u00e9 pour le routage" + "individual_address": "Adresse individuelle", + "local_ip": "IP locale de Home Assistant", + "multicast_group": "Groupe multicast", + "multicast_port": "Port multicast" + }, + "data_description": { + "individual_address": "Adresse KNX que Home Assistant doit utiliser, par exemple `0.0.4`.", + "local_ip": "Laissez le champ vide pour utiliser la d\u00e9couverte automatique." }, "description": "Veuillez configurer les options de routage." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Le nom de votre fichier `.knxkeys` (extension incluse)", + "knxkeys_password": "Le mot de passe pour d\u00e9chiffrer le fichier `.knxkeys`" + }, + "data_description": { + "knxkeys_filename": "Le fichier devrait se trouver dans votre r\u00e9pertoire de configuration dans `.storage/knx/`.\nSous Home Assistant OS, il s'agirait de `/config/.storage/knx/`\nPar exemple\u00a0: `my_project.knxkeys`", + "knxkeys_password": "D\u00e9fini lors de l'exportation du fichier depuis ETS." + }, + "description": "Veuillez saisir les informations relatives \u00e0 votre fichier `.knxkeys`." + }, + "secure_manual": { + "data": { + "device_authentication": "Mot de passe d'authentification de l'appareil", + "user_id": "ID de l'utilisateur", + "user_password": "Mot de passe de l'utilisateur" + }, + "data_description": { + "device_authentication": "D\u00e9fini dans le panneau \u00ab\u00a0IP\u00a0\u00bb de l'interface dans ETS.", + "user_id": "G\u00e9n\u00e9ralement le num\u00e9ro du tunnel +\u00a01. Par exemple, \u00ab\u00a0Tunnel 2\u00a0\u00bb aurait l'ID utilisateur \u00ab\u00a03\u00a0\u00bb.", + "user_password": "Mot de passe pour la connexion de tunnel sp\u00e9cifique, d\u00e9fini dans le panneau \u00ab\u00a0Propri\u00e9t\u00e9s\u00a0\u00bb du tunnel dans ETS." + }, + "description": "Veuillez saisir vos informations de s\u00e9curit\u00e9 IP." + }, + "secure_tunneling": { + "description": "S\u00e9lectionnez la mani\u00e8re dont vous souhaitez configurer la s\u00e9curit\u00e9 IP de KNX.", + "menu_options": { + "secure_knxkeys": "Utiliser un fichier `.knxkeys` contenant les cl\u00e9s de s\u00e9curit\u00e9 IP", + "secure_manual": "Configurer manuellement les cl\u00e9s de s\u00e9curit\u00e9 IP" + } + }, "tunnel": { "data": { "gateway": "Connexion tunnel KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "Type de connexion KNX", "individual_address": "Adresse individuelle par d\u00e9faut", - "local_ip": "IP locale de Home Assistant (utilisez 0.0.0.0 pour la d\u00e9tection automatique)", - "multicast_group": "Groupe de multidiffusion utilis\u00e9 pour le routage et la d\u00e9couverte", - "multicast_port": "Port de multidiffusion utilis\u00e9 pour le routage et la d\u00e9couverte", - "rate_limit": "Nombre maximal de t\u00e9l\u00e9grammes sortants par seconde", - "state_updater": "Activer globalement la lecture des \u00e9tats depuis le bus KNX" + "local_ip": "IP locale de Home Assistant", + "multicast_group": "Groupe multicast", + "multicast_port": "Port multicast", + "rate_limit": "Limite d'envoi", + "state_updater": "Mises \u00e0 jour d'\u00e9tat" + }, + "data_description": { + "individual_address": "Adresse KNX que Home Assistant doit utiliser, par exemple `0.0.4`.", + "local_ip": "Utilisez `0.0.0.0` pour la d\u00e9couverte automatique.", + "multicast_group": "Utilis\u00e9 pour le routage et la d\u00e9couverte. Valeur par d\u00e9faut\u00a0: `224.0.23.12`", + "multicast_port": "Utilis\u00e9 pour le routage et la d\u00e9couverte. Valeur par d\u00e9faut\u00a0: `3671`", + "rate_limit": "Nombre maximal de t\u00e9l\u00e9grammes sortants par seconde.\nValeur recommand\u00e9e\u00a0: entre 20 et 40", + "state_updater": "Active ou d\u00e9sactive globalement la lecture des \u00e9tats depuis le bus KNX. Lorsqu'elle est d\u00e9sactiv\u00e9e, Home Assistant ne r\u00e9cup\u00e8re pas activement les \u00e9tats depuis le bus KNX et les options d'entit\u00e9 `sync_state` n'ont aucun effet." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Retour/Mode NAT", "tunneling_type": "Type de tunnel KNX" + }, + "data_description": { + "host": "Adresse IP de l'appareil de tunnel KNX/IP.", + "port": "Port de l'appareil de tunnel KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/hu.json b/homeassistant/components/knx/translations/hu.json index 13bd4650378..02b8b0a0465 100644 --- a/homeassistant/components/knx/translations/hu.json +++ b/homeassistant/components/knx/translations/hu.json @@ -5,29 +5,73 @@ "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "file_not_found": "A megadott '.knxkeys' f\u00e1jl nem tal\u00e1lhat\u00f3 a config/.storage/knx/ el\u00e9r\u00e9si \u00fatvonalon.", + "invalid_individual_address": "Az \u00e9rt\u00e9k nem felel meg a KNX egyedi c\u00edm mint\u00e1j\u00e1nak.\n'area.line.device'", + "invalid_ip_address": "\u00c9rv\u00e9nytelen IPv4-c\u00edm.", + "invalid_signature": "A '.knxkeys' f\u00e1jl visszafejt\u00e9s\u00e9hez haszn\u00e1lt jelsz\u00f3 helytelen." }, "step": { "manual_tunnel": { "data": { "host": "C\u00edm", "individual_address": "A kapcsolat egy\u00e9ni c\u00edme", - "local_ip": "Helyi IP c\u00edm (hagyja \u00fcresen, ha nem biztos benne)", + "local_ip": "Home Assistant lok\u00e1lis IP c\u00edme", "port": "Port", "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d", "tunneling_type": "KNX alag\u00fat t\u00edpusa" }, + "data_description": { + "host": "A KNX/IP tunnel eszk\u00f6z IP-c\u00edme.", + "local_ip": "Az automatikus felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz hagyja \u00fcresen.", + "port": "A KNX/IP tunnel eszk\u00f6z portsz\u00e1ma." + }, "description": "Adja meg az alag\u00fatkezel\u0151 (tunneling) eszk\u00f6z csatlakoz\u00e1si adatait." }, "routing": { "data": { - "individual_address": "Az \u00fatv\u00e1laszt\u00e1si (routing) kapcsolat egy\u00e9ni c\u00edme", - "local_ip": "Helyi IP c\u00edm (hagyja \u00fcresen, ha nem biztos benne)", - "multicast_group": "Az \u00fatv\u00e1laszt\u00e1shoz haszn\u00e1lt multicast csoport", - "multicast_port": "Az \u00fatv\u00e1laszt\u00e1shoz haszn\u00e1lt multicast portsz\u00e1m" + "individual_address": "Egy\u00e9ni c\u00edm", + "local_ip": "Home Assistant lok\u00e1lis IP c\u00edme", + "multicast_group": "Multicast csoport", + "multicast_port": "Multicast portsz\u00e1m" + }, + "data_description": { + "individual_address": "A Home Assistant \u00e1ltal haszn\u00e1land\u00f3 KNX-c\u00edm, pl. \"0.0.4\".", + "local_ip": "Az automatikus felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz hagyja \u00fcresen." }, "description": "K\u00e9rem, konfigur\u00e1lja az \u00fatv\u00e1laszt\u00e1si (routing) be\u00e1ll\u00edt\u00e1sokat." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "A '.knxkeys' f\u00e1jl teljes neve (kiterjeszt\u00e9ssel)", + "knxkeys_password": "A '.knxkeys' f\u00e1jl visszafejt\u00e9s\u00e9hez sz\u00fcks\u00e9ges jelsz\u00f3" + }, + "data_description": { + "knxkeys_filename": "A f\u00e1jl a `.storage/knx/` konfigur\u00e1ci\u00f3s k\u00f6nyvt\u00e1r\u00e1ban helyezend\u0151.\nHome Assistant oper\u00e1ci\u00f3s rendszer eset\u00e9n ez a k\u00f6vetkez\u0151 lenne: `/config/.storage/knx/`\nP\u00e9lda: \"my_project.knxkeys\".", + "knxkeys_password": "Ez a be\u00e1ll\u00edt\u00e1s a f\u00e1jl ETS-b\u0151l t\u00f6rt\u00e9n\u0151 export\u00e1l\u00e1sakor t\u00f6rt\u00e9nt." + }, + "description": "K\u00e9rj\u00fck, adja meg a '.knxkeys' f\u00e1jl adatait." + }, + "secure_manual": { + "data": { + "device_authentication": "Eszk\u00f6z hiteles\u00edt\u00e9si jelsz\u00f3", + "user_id": "Felhaszn\u00e1l\u00f3i azonos\u00edt\u00f3", + "user_password": "Felhaszn\u00e1l\u00f3i jelsz\u00f3" + }, + "data_description": { + "device_authentication": "Ezt az ETS-ben az interf\u00e9sz \"IP\" panelj\u00e9n kell be\u00e1ll\u00edtani.", + "user_id": "Ez gyakran a tunnel sz\u00e1ma +1. Teh\u00e1t a \"Tunnel 2\" felhaszn\u00e1l\u00f3i azonos\u00edt\u00f3ja \"3\".", + "user_password": "Jelsz\u00f3 az adott tunnelhez, amely a tunnel \u201eProperties\u201d panelj\u00e9n van be\u00e1ll\u00edtva az ETS-ben." + }, + "description": "K\u00e9rj\u00fck, adja meg az IP secure adatokat." + }, + "secure_tunneling": { + "description": "V\u00e1lassza ki, hogyan szeretn\u00e9 konfigur\u00e1lni az KNX/IP secure-t.", + "menu_options": { + "secure_knxkeys": "IP secure kulcsokat tartalmaz\u00f3 '.knxkeys' f\u00e1jl haszn\u00e1lata", + "secure_manual": "IP secure kulcsok manu\u00e1lis be\u00e1ll\u00edt\u00e1sa" + } + }, "tunnel": { "data": { "gateway": "KNX alag\u00fat (tunnel) kapcsolat" @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX csatlakoz\u00e1s t\u00edpusa", "individual_address": "Alap\u00e9rtelmezett egy\u00e9ni c\u00edm", - "local_ip": "Helyi IP c\u00edm (hagyja \u00fcresen, ha nem biztos benne)", - "multicast_group": "\u00datv\u00e1laszt\u00e1shoz \u00e9s felder\u00edt\u00e9shez haszn\u00e1lt multicast csoport", - "multicast_port": "\u00datv\u00e1laszt\u00e1shoz \u00e9s felder\u00edt\u00e9shez haszn\u00e1lt multicast portsz\u00e1m\n", - "rate_limit": "Maxim\u00e1lis kimen\u0151 \u00fczenet darabsz\u00e1m m\u00e1sodpercenk\u00e9nt", - "state_updater": "Glob\u00e1lisan enged\u00e9lyezi az \u00e1llapotok olvas\u00e1s\u00e1t a KNX buszr\u00f3l." + "local_ip": "Home Assistant lok\u00e1lis IP c\u00edme", + "multicast_group": "Multicast csoport", + "multicast_port": "Multicast portsz\u00e1m", + "rate_limit": "Lek\u00e9r\u00e9si korl\u00e1toz\u00e1s", + "state_updater": "\u00c1llapot friss\u00edt\u0151" + }, + "data_description": { + "individual_address": "A Home Assistant \u00e1ltal haszn\u00e1land\u00f3 KNX-c\u00edm, pl. \"0.0.4\".", + "local_ip": "Haszn\u00e1lja a `0.0.0.0` c\u00edmet az automatikus felder\u00edt\u00e9shez.", + "multicast_group": "\u00datv\u00e1laszt\u00e1shoz \u00e9s felder\u00edt\u00e9shez haszn\u00e1latos. Alap\u00e9rtelmezett: `224.0.23.12`.", + "multicast_port": "\u00datv\u00e1laszt\u00e1shoz \u00e9s felder\u00edt\u00e9shez haszn\u00e1latos. Alap\u00e9rtelmezett: `3671`", + "rate_limit": "Maxim\u00e1lis kimen\u0151 \u00fczenet m\u00e1sodpercenk\u00e9nt.\nAj\u00e1nlott: 20 \u00e9s 40 k\u00f6z\u00f6tt", + "state_updater": "Glob\u00e1lisan enged\u00e9lyezze vagy tiltsa le az olvas\u00e1si \u00e1llapotokat a KNX-buszr\u00f3l. Ha le van tiltva, a Home Assistant nem fogja akt\u00edvan lek\u00e9rni az \u00e1llapotokat a KNX-buszr\u00f3l, a \"sync_state\" entit\u00e1sopci\u00f3knak nincs hat\u00e1sa." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "\u00datvonal (route) vissza / NAT m\u00f3d", "tunneling_type": "KNX alag\u00fat t\u00edpusa" + }, + "data_description": { + "host": "A KNX/IP tunnel eszk\u00f6z IP-c\u00edme.", + "port": "A KNX/IP tunnel eszk\u00f6z portsz\u00e1ma." } } } diff --git a/homeassistant/components/knx/translations/id.json b/homeassistant/components/knx/translations/id.json index 1cdb614a3cc..92d812c1b1b 100644 --- a/homeassistant/components/knx/translations/id.json +++ b/homeassistant/components/knx/translations/id.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "error": { - "cannot_connect": "Gagal terhubung" + "cannot_connect": "Gagal terhubung", + "file_not_found": "File `.knxkeys` yang ditentukan tidak ditemukan di jalur config/.storage/knx/", + "invalid_individual_address": "Nilai tidak cocok dengan pola untuk alamat individual KNX.\n'area.line.device'", + "invalid_ip_address": "Alamat IPv4 tidak valid", + "invalid_signature": "Kata sandi untuk mendekripsi file `.knxkeys` salah." }, "step": { "manual_tunnel": { "data": { "host": "Host", "individual_address": "Alamat individu untuk koneksi", - "local_ip": "IP lokal Home Assistant (kosongkan jika tidak yakin)", + "local_ip": "IP lokal Home Assistant", "port": "Port", "route_back": "Dirutekan Kembali/Mode NAT", "tunneling_type": "Jenis Tunnel KNX" }, + "data_description": { + "host": "Alamat IP perangkat tunneling KNX/IP.", + "local_ip": "Kosongkan untuk menggunakan penemuan otomatis.", + "port": "Port perangkat tunneling KNX/IP." + }, "description": "Masukkan informasi koneksi untuk perangkat tunneling Anda." }, "routing": { "data": { - "individual_address": "Alamat individu untuk koneksi routing", - "local_ip": "IP lokal Home Assistant (kosongkan jika tidak yakin)", - "multicast_group": "Grup multicast yang digunakan untuk routing", - "multicast_port": "Port multicast yang digunakan untuk routing" + "individual_address": "Alamat individual", + "local_ip": "IP lokal Home Assistant", + "multicast_group": "Grup multicast", + "multicast_port": "Port multicast" + }, + "data_description": { + "individual_address": "Alamat KNX yang akan digunakan oleh Home Assistant, misalnya `0.0.4`", + "local_ip": "Kosongkan untuk menggunakan penemuan otomatis." }, "description": "Konfigurasikan opsi routing." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Nama file '.knxkeys' Anda (termasuk ekstensi)", + "knxkeys_password": "Kata sandi untuk mendekripsi file `.knxkeys`" + }, + "data_description": { + "knxkeys_filename": "File diharapkan dapat ditemukan di direktori konfigurasi Anda di `.storage/knx/`.\nDi Home Assistant OS ini akan menjadi `/config/.storage/knx/`\nContoh: `proyek_saya.knxkeys`", + "knxkeys_password": "Ini disetel saat mengekspor file dari ETS." + }, + "description": "Masukkan informasi untuk file `.knxkeys` Anda." + }, + "secure_manual": { + "data": { + "device_authentication": "Kata sandi autentikasi perangkat", + "user_id": "ID pengguna", + "user_password": "Kata sandi pengguna" + }, + "data_description": { + "device_authentication": "Ini diatur dalam panel 'IP' dalam antarmuka di ETS.", + "user_id": "Ini sering kali merupakan tunnel nomor +1. Jadi 'Tunnel 2' akan memiliki User-ID '3'.", + "user_password": "Kata sandi untuk koneksi tunnel tertentu yang diatur di panel 'Properties' tunnel di ETS." + }, + "description": "Masukkan informasi IP aman Anda." + }, + "secure_tunneling": { + "description": "Pilih cara Anda ingin mengonfigurasi KNX/IP Secure.", + "menu_options": { + "secure_knxkeys": "Gunakan file `.knxkeys` yang berisi kunci aman IP", + "secure_manual": "Konfigurasikan kunci aman IP secara manual" + } + }, "tunnel": { "data": { "gateway": "Koneksi Tunnel KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "Jenis Koneksi KNX", "individual_address": "Alamat individu default", - "local_ip": "IP lokal Home Assistant (gunakan 0.0.0.0 untuk deteksi otomatis)", - "multicast_group": "Grup multicast yang digunakan untuk routing dan penemuan", - "multicast_port": "Port multicast yang digunakan untuk routing dan penemuan", - "rate_limit": "Jumlah maksimal telegram keluar per detik", - "state_updater": "Aktifkan status membaca secara global dari KNX Bus" + "local_ip": "IP lokal Home Assistant", + "multicast_group": "Grup multicast", + "multicast_port": "Port multicast", + "rate_limit": "Batas data", + "state_updater": "Pembaruan status" + }, + "data_description": { + "individual_address": "Alamat KNX yang akan digunakan oleh Home Assistant, misalnya `0.0.4`", + "local_ip": "Gunakan `0.0.0.0` untuk penemuan otomatis.", + "multicast_group": "Digunakan untuk perutean dan penemuan. Bawaan: `224.0.23.12`", + "multicast_port": "Digunakan untuk perutean dan penemuan. Bawaan: `3671`", + "rate_limit": "Telegram keluar maksimum per detik.\nDirekomendasikan: 20 hingga 40", + "state_updater": "Secara global mengaktifkan atau menonaktifkan status pembacaan dari KNX Bus. Saat dinonaktifkan, Home Assistant tidak akan secara aktif mengambil status dari KNX Bus, opsi entitas 'sync_state' tidak akan berpengaruh." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Dirutekan Kembali/Mode NAT", "tunneling_type": "Jenis Tunnel KNX" + }, + "data_description": { + "host": "Alamat IP perangkat tunneling KNX/IP.", + "port": "Port perangkat tunneling KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/it.json b/homeassistant/components/knx/translations/it.json index ad4e9f34610..c05bdba3944 100644 --- a/homeassistant/components/knx/translations/it.json +++ b/homeassistant/components/knx/translations/it.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { - "cannot_connect": "Impossibile connettersi" + "cannot_connect": "Impossibile connettersi", + "file_not_found": "Il file `.knxkeys` specificato non \u00e8 stato trovato nel percorso config/.storage/knx/", + "invalid_individual_address": "Il valore non corrisponde al modello per l'indirizzo individuale KNX. 'area.line.device'", + "invalid_ip_address": "Indirizzo IPv4 non valido.", + "invalid_signature": "La password per decifrare il file `.knxkeys` \u00e8 errata." }, "step": { "manual_tunnel": { "data": { "host": "Host", "individual_address": "Indirizzo individuale per la connessione", - "local_ip": "IP locale di Home Assistant (lascia vuoto per il rilevamento automatico)", + "local_ip": "IP locale di Home Assistant", "port": "Porta", "route_back": "Torna indietro / Modalit\u00e0 NAT", "tunneling_type": "Tipo tunnel KNX" }, + "data_description": { + "host": "Indirizzo IP del dispositivo di tunneling KNX/IP.", + "local_ip": "Lascia vuoto per usare il rilevamento automatico.", + "port": "Porta del dispositivo di tunneling KNX/IP." + }, "description": "Inserisci le informazioni di connessione del tuo dispositivo di tunneling." }, "routing": { "data": { - "individual_address": "Indirizzo individuale per la connessione di routing", - "local_ip": "IP locale di Home Assistant (lascia vuoto per il rilevamento automatico)", - "multicast_group": "Il gruppo multicast utilizzato per il routing", - "multicast_port": "La porta multicast usata per il routing" + "individual_address": "Indirizzo individuale", + "local_ip": "IP locale di Home Assistant", + "multicast_group": "Gruppo multicast", + "multicast_port": "Porta multicast" + }, + "data_description": { + "individual_address": "Indirizzo KNX che deve essere utilizzato da Home Assistant, ad es. `0.0.4`", + "local_ip": "Lasciare vuoto per usare il rilevamento automatico." }, "description": "Configura le opzioni di instradamento." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Il nome del file `.knxkeys` (inclusa l'estensione)", + "knxkeys_password": "La password per decifrare il file `.knxkeys`" + }, + "data_description": { + "knxkeys_filename": "Il file dovrebbe essere trovato nella tua cartella di configurazione in `.storage/knx/`.\n Nel sistema operativo Home Assistant questo sarebbe `/config/.storage/knx/`\n Esempio: `mio_progetto.knxkeys`", + "knxkeys_password": "Questo \u00e8 stato impostato durante l'esportazione del file da ETS." + }, + "description": "Inserisci le informazioni per il tuo file `.knxkeys`." + }, + "secure_manual": { + "data": { + "device_authentication": "Password di autenticazione del dispositivo", + "user_id": "ID utente", + "user_password": "Password utente" + }, + "data_description": { + "device_authentication": "Questo \u00e8 impostato nel pannello 'IP' dell'interfaccia in ETS.", + "user_id": "Questo \u00e8 spesso il tunnel numero +1. Quindi \"Tunnel 2\" avrebbe l'ID utente \"3\".", + "user_password": "Password per la connessione specifica del tunnel impostata nel pannello 'Propriet\u00e0' del tunnel in ETS." + }, + "description": "Inserisci le tue informazioni di sicurezza IP." + }, + "secure_tunneling": { + "description": "Seleziona come vuoi configurare KNX/IP Secure.", + "menu_options": { + "secure_knxkeys": "Utilizza un file `.knxkeys` contenente chiavi di sicurezza IP", + "secure_manual": "Configura manualmente le chiavi di sicurezza IP" + } + }, "tunnel": { "data": { "gateway": "Connessione tunnel KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "Tipo di connessione KNX", "individual_address": "Indirizzo individuale predefinito", - "local_ip": "IP locale di Home Assistant (usare 0.0.0.0 per il rilevamento automatico)", - "multicast_group": "Gruppo multicast utilizzato per il routing e il rilevamento", - "multicast_port": "Porta multicast utilizzata per il routing e il rilevamento", - "rate_limit": "Numero massimo di telegrammi in uscita al secondo", - "state_updater": "Abilita globalmente la lettura degli stati dal bus KNX" + "local_ip": "IP locale di Home Assistant", + "multicast_group": "Gruppo multicast", + "multicast_port": "Porta multicast", + "rate_limit": "Limite di tariffa", + "state_updater": "Aggiornatore di stato" + }, + "data_description": { + "individual_address": "Indirizzo KNX che deve essere utilizzato da Home Assistant, ad es. `0.0.4`", + "local_ip": "Usa `0.0.0.0` per il rilevamento automatico.", + "multicast_group": "Utilizzato per l'instradamento e il rilevamento. Predefinito: `224.0.23.12`", + "multicast_port": "Utilizzato per l'instradamento e il rilevamento. Predefinito: `3671`", + "rate_limit": "Numero massimo di telegrammi in uscita al secondo.\n Consigliato: da 20 a 40", + "state_updater": "Abilita o disabilita globalmente gli stati di lettura dal bus KNX. Se disabilitato, Home Assistant non recuperer\u00e0 attivamente gli stati dal bus KNX, le opzioni dell'entit\u00e0 `sync_state` non avranno alcun effetto." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Porta", "route_back": "Torna indietro / Modalit\u00e0 NAT", "tunneling_type": "Tipo tunnel KNX" + }, + "data_description": { + "host": "Indirizzo IP del dispositivo di tunneling KNX/IP.", + "port": "Porta del dispositivo di tunneling KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/ja.json b/homeassistant/components/knx/translations/ja.json index a4744a41a2c..52eed8780b8 100644 --- a/homeassistant/components/knx/translations/ja.json +++ b/homeassistant/components/knx/translations/ja.json @@ -5,7 +5,11 @@ "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" }, "error": { - "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f" + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "file_not_found": "\u6307\u5b9a\u3055\u308c\u305f'.knxkeys'\u30d5\u30a1\u30a4\u30eb\u304c\u3001\u30d1\u30b9: config/.storage/knx/ \u306b\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "invalid_individual_address": "\u5024\u304cKNX\u500b\u5225\u30a2\u30c9\u30ec\u30b9\u306e\u30d1\u30bf\u30fc\u30f3\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002\n'area.line.device'", + "invalid_ip_address": "IPv4\u30a2\u30c9\u30ec\u30b9\u304c\u7121\u52b9\u3067\u3059\u3002", + "invalid_signature": "'.knxkeys'\u30d5\u30a1\u30a4\u30eb\u3092\u5fa9\u53f7\u5316\u3059\u308b\u305f\u3081\u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059\u3002" }, "step": { "manual_tunnel": { @@ -17,6 +21,11 @@ "route_back": "\u30eb\u30fc\u30c8\u30d0\u30c3\u30af / NAT\u30e2\u30fc\u30c9", "tunneling_type": "KNX\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30bf\u30a4\u30d7" }, + "data_description": { + "host": "KNX/IP\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30c7\u30d0\u30a4\u30b9\u306eIP\u30a2\u30c9\u30ec\u30b9\u3002", + "local_ip": "\u81ea\u52d5\u691c\u51fa\u3092\u4f7f\u7528\u3059\u308b\u306b\u306f\u3001\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002", + "port": "KNX/IP\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30c7\u30d0\u30a4\u30b9\u306e\u30dd\u30fc\u30c8\u3002" + }, "description": "\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30c7\u30d0\u30a4\u30b9\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "routing": { @@ -26,8 +35,43 @@ "multicast_group": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u306b\u4f7f\u7528\u3059\u308b\u30de\u30eb\u30c1\u30ad\u30e3\u30b9\u30c8\u30b0\u30eb\u30fc\u30d7", "multicast_port": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u306b\u4f7f\u7528\u3059\u308b\u30de\u30eb\u30c1\u30ad\u30e3\u30b9\u30c8\u30dd\u30fc\u30c8" }, + "data_description": { + "individual_address": "Home Assistant\u304c\u4f7f\u7528\u3059\u308bKNX\u30a2\u30c9\u30ec\u30b9\u3001\u4f8b. `0.0.4`", + "local_ip": "\u81ea\u52d5\u691c\u51fa\u3092\u4f7f\u7528\u3059\u308b\u306b\u306f\u3001\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002" + }, "description": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "'.knxkeys'\u30d5\u30a1\u30a4\u30eb\u306e\u30d5\u30a1\u30a4\u30eb\u540d(\u62e1\u5f35\u5b50\u3092\u542b\u3080)", + "knxkeys_password": "'.knxkeys'\u30d5\u30a1\u30a4\u30eb\u3092\u5fa9\u53f7\u5316\u3059\u308b\u305f\u3081\u306e\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "data_description": { + "knxkeys_filename": "\u3053\u306e\u30d5\u30a1\u30a4\u30eb\u306f\u3001config\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e `.storage/knx/` \u306b\u3042\u308b\u306f\u305a\u3067\u3059\u3002\nHome Assistant OS\u306e\u5834\u5408\u3001\u3053\u308c\u306f\u3001`/config/.storage/knx/` \u306b\u306a\u308a\u307e\u3059\n\u4f8b: `my_project.knxkeys`", + "knxkeys_password": "\u3053\u308c\u306f\u3001ETS\u304b\u3089\u30d5\u30a1\u30a4\u30eb\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3059\u308b\u3068\u304d\u306b\u8a2d\u5b9a\u3055\u308c\u307e\u3057\u305f\u3002" + }, + "description": "'.knxkeys'\u30d5\u30a1\u30a4\u30eb\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, + "secure_manual": { + "data": { + "device_authentication": "\u30c7\u30d0\u30a4\u30b9\u8a8d\u8a3c\u30d1\u30b9\u30ef\u30fc\u30c9", + "user_id": "\u30e6\u30fc\u30b6\u30fcID", + "user_password": "\u30e6\u30fc\u30b6\u30fc\u30d1\u30b9\u30ef\u30fc\u30c9" + }, + "data_description": { + "device_authentication": "\u3053\u308c\u306f\u3001ETS\u306e\u30a4\u30f3\u30bf\u30fc\u30d5\u30a7\u30fc\u30b9\u306e 'IP' \u30d1\u30cd\u30eb\u3067\u8a2d\u5b9a\u3057\u307e\u3059\u3002", + "user_id": "\u591a\u304f\u306e\u5834\u5408\u3001\u3053\u308c\u306f\u30c8\u30f3\u30cd\u30eb\u756a\u53f7+1\u3067\u3059\u3002\u3057\u305f\u304c\u3063\u3066\u3001 '\u30c8\u30f3\u30cd\u30eb2' \u306e\u30e6\u30fc\u30b6\u30fcID\u306f\u3001'3 '\u306b\u306a\u308a\u307e\u3059\u3002", + "user_password": "ETS\u306e\u30c8\u30f3\u30cd\u30eb\u306e\u3001'\u30d7\u30ed\u30d1\u30c6\u30a3' \u30d1\u30cd\u30eb\u3067\u8a2d\u5b9a\u3055\u308c\u305f\u7279\u5b9a\u306e\u30c8\u30f3\u30cd\u30eb\u63a5\u7d9a\u7528\u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u3002" + }, + "description": "IP\u30bb\u30ad\u30e5\u30a2\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, + "secure_tunneling": { + "description": "IP\u30bb\u30ad\u30e5\u30a2\u306e\u8a2d\u5b9a\u65b9\u6cd5\u3092\u9078\u629e\u3057\u307e\u3059\u3002", + "menu_options": { + "secure_knxkeys": "IP\u30bb\u30ad\u30e5\u30a2\u60c5\u5831\u3092\u542b\u3080knxkeys\u30d5\u30a1\u30a4\u30eb\u3092\u8a2d\u5b9a\u3057\u307e\u3059", + "secure_manual": "IP\u30bb\u30ad\u30e5\u30a2\u3092\u624b\u52d5\u3067\u8a2d\u5b9a\u3059\u308b" + } + }, "tunnel": { "data": { "gateway": "KNX\u30c8\u30f3\u30cd\u30eb\u63a5\u7d9a" @@ -53,6 +97,14 @@ "multicast_port": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u3068\u691c\u51fa(discovery)\u306b\u4f7f\u7528\u3055\u308c\u308b\u30de\u30eb\u30c1\u30ad\u30e3\u30b9\u30c8\u30dd\u30fc\u30c8", "rate_limit": "1 \u79d2\u3042\u305f\u308a\u306e\u6700\u5927\u9001\u4fe1\u96fb\u5831(telegrams )\u6570", "state_updater": "KNX\u30d0\u30b9\u304b\u3089\u306e\u8aad\u307f\u53d6\u308a\u72b6\u614b\u3092\u30b0\u30ed\u30fc\u30d0\u30eb\u306b\u6709\u52b9\u306b\u3059\u308b" + }, + "data_description": { + "individual_address": "Home Assistant\u304c\u4f7f\u7528\u3059\u308bKNX\u30a2\u30c9\u30ec\u30b9\u3001\u4f8b. `0.0.4`", + "local_ip": "\u81ea\u52d5\u691c\u51fa\u306b\u306f\u3001`0.0.0.0` \u3092\u4f7f\u7528\u3057\u307e\u3059\u3002", + "multicast_group": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u3068\u691c\u51fa\u306b\u4f7f\u7528\u3055\u308c\u307e\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8t: `224.0.23.12`", + "multicast_port": "\u30eb\u30fc\u30c6\u30a3\u30f3\u30b0\u3068\u691c\u51fa\u306b\u4f7f\u7528\u3055\u308c\u307e\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8: `3671`", + "rate_limit": "1\u79d2\u3042\u305f\u308a\u306e\u6700\u5927\u9001\u4fe1\u30c6\u30ec\u30b0\u30e9\u30e0\u3002\n\u63a8\u5968: 20\uff5e40", + "state_updater": "KNX Bus\u304b\u3089\u306e\u72b6\u614b\u306e\u8aad\u307f\u53d6\u308a\u3092\u30b0\u30ed\u30fc\u30d0\u30eb\u306b\u6709\u52b9\u307e\u305f\u306f\u7121\u52b9\u306b\u3057\u307e\u3059\u3002\u7121\u52b9\u306b\u3059\u308b\u3068\u3001Home Assistant\u306f\u3001KNX Bus\u304b\u3089\u30a2\u30af\u30c6\u30a3\u30d6\u306b\u72b6\u614b\u3092\u53d6\u5f97\u3057\u306a\u304f\u306a\u308b\u306e\u3067\u3001`sync_state`\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u610f\u5473\u3092\u6301\u305f\u306a\u304f\u306a\u308a\u307e\u3059\u3002" } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "\u30dd\u30fc\u30c8", "route_back": "\u30eb\u30fc\u30c8\u30d0\u30c3\u30af / NAT\u30e2\u30fc\u30c9", "tunneling_type": "KNX\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30bf\u30a4\u30d7" + }, + "data_description": { + "host": "KNX/IP\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30c7\u30d0\u30a4\u30b9\u306eIP\u30a2\u30c9\u30ec\u30b9\u3002", + "port": "KNX/IP\u30c8\u30f3\u30cd\u30ea\u30f3\u30b0\u30c7\u30d0\u30a4\u30b9\u306e\u30dd\u30fc\u30c8\u3002" } } } diff --git a/homeassistant/components/knx/translations/nl.json b/homeassistant/components/knx/translations/nl.json index 9b68bd02d2f..f40b9d7729f 100644 --- a/homeassistant/components/knx/translations/nl.json +++ b/homeassistant/components/knx/translations/nl.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { - "cannot_connect": "Kan geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken", + "file_not_found": "Het opgegeven `.knxkeys`-bestand is niet gevonden in het pad config/.storage/knx/", + "invalid_individual_address": "Waarde komt niet overeen met patroon voor KNX individueel adres.\n\"area.line.device", + "invalid_ip_address": "Ongeldig IPv4-adres.", + "invalid_signature": "Het wachtwoord om het `.knxkeys`-bestand te decoderen is verkeerd." }, "step": { "manual_tunnel": { "data": { "host": "Host", "individual_address": "Individueel adres voor de verbinding", - "local_ip": "Lokaal IP van Home Assistant (leeg laten voor automatische detectie)", + "local_ip": "Lokale IP van Home Assistant", "port": "Poort", "route_back": "Route Back / NAT Mode", "tunneling_type": "KNX Tunneling Type" }, + "data_description": { + "host": "IP adres van het KNX/IP tunneling apparaat.", + "local_ip": "Leeg laten om auto-discovery te gebruiken.", + "port": "Poort van het KNX/IP-tunnelapparaat." + }, "description": "Voer de verbindingsinformatie van uw tunneling-apparaat in." }, "routing": { "data": { - "individual_address": "Individueel adres voor de routing verbinding", - "local_ip": "Lokaal IP van Home Assistant (leeg laten voor automatische detectie)", - "multicast_group": "De multicast groep gebruikt voor de routing", - "multicast_port": "De multicast-poort gebruikt voor de routing" + "individual_address": "Individueel adres", + "local_ip": "Lokale IP van Home Assistant", + "multicast_group": "Multicast-groep", + "multicast_port": "Multicast-poort" + }, + "data_description": { + "individual_address": "KNX-adres te gebruiken door Home Assistant, bijv. `0.0.4`", + "local_ip": "Leeg laten om auto-discovery te gebruiken." }, "description": "Configureer de routing opties" }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "De bestandsnaam van uw `.knxkeys` bestand (inclusief extensie)", + "knxkeys_password": "Het wachtwoord om het bestand `.knxkeys` te ontcijferen" + }, + "data_description": { + "knxkeys_filename": "Het bestand zal naar verwachting worden gevonden in uw configuratiemap in '.storage/knx/'.\nIn Home Assistant OS zou dit '/config/.storage/knx/' zijn.\nVoorbeeld: 'my_project.knxkeys'", + "knxkeys_password": "Dit werd ingesteld bij het exporteren van het bestand van ETS." + }, + "description": "Voer de informatie voor uw `.knxkeys` bestand in." + }, + "secure_manual": { + "data": { + "device_authentication": "Wachtwoord voor apparaatverificatie", + "user_id": "User ID", + "user_password": "Gebruikerswachtwoord" + }, + "data_description": { + "device_authentication": "Dit wordt ingesteld in het \"IP\"-paneel van de interface in ETS.", + "user_id": "Dit is vaak tunnelnummer +1. Dus 'Tunnel 2' zou User-ID '3' hebben.", + "user_password": "Wachtwoord voor de specifieke tunnelverbinding, ingesteld in het paneel \"Eigenschappen\" van de tunnel in ETS." + }, + "description": "Voer uw beveiligde IP-gegevens in." + }, + "secure_tunneling": { + "description": "Kies hoe u KNX/IP Secure wilt configureren.", + "menu_options": { + "secure_knxkeys": "Gebruik een `.knxkeys` bestand met IP beveiligde sleutels", + "secure_manual": "IP-beveiligingssleutels handmatig configureren" + } + }, "tunnel": { "data": { "gateway": "KNX Tunnel Connection" @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX-verbindingstype", "individual_address": "Standaard individueel adres", - "local_ip": "Lokaal IP van Home Assistant (gebruik 0.0.0.0 voor automatische detectie)", - "multicast_group": "Multicast groep gebruikt voor routing en ontdekking", - "multicast_port": "Multicast poort gebruikt voor routing en ontdekking", - "rate_limit": "Maximaal aantal uitgaande telegrammen per seconde", - "state_updater": "Globaal vrijgeven van het lezen van de KNX bus" + "local_ip": "Lokale IP van Home Assistant", + "multicast_group": "Multicast-groep", + "multicast_port": "Multicast-poort", + "rate_limit": "Rate limit", + "state_updater": "Statusupdater" + }, + "data_description": { + "individual_address": "KNX-adres dat door Home Assistant moet worden gebruikt, bijv. `0.0.4`", + "local_ip": "Gebruik `0.0.0.0` voor auto-discovery.", + "multicast_group": "Gebruikt voor routing en discovery. Standaard: `224.0.23.12`.", + "multicast_port": "Gebruikt voor routing en discovery. Standaard: `3671`", + "rate_limit": "Maximaal aantal uitgaande telegrammen per seconde.\nAanbevolen: 20 tot 40", + "state_updater": "Globaal in- of uitschakelen van het lezen van de status van de KNX bus. Indien uitgeschakeld, zal Home Assistant niet actief de status van de KNX Bus ophalen, `sync_state` entiteitsopties zullen geen effect hebben." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Poort", "route_back": "Route Back / NAT Mode", "tunneling_type": "KNX Tunneling Type" + }, + "data_description": { + "host": "IP adres van het KNX/IP tunneling apparaat.", + "port": "Poort van het KNX/IP-tunnelapparaat." } } } diff --git a/homeassistant/components/knx/translations/no.json b/homeassistant/components/knx/translations/no.json index 231945d5233..f5d03e3160c 100644 --- a/homeassistant/components/knx/translations/no.json +++ b/homeassistant/components/knx/translations/no.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "cannot_connect": "Tilkobling mislyktes" + "cannot_connect": "Tilkobling mislyktes", + "file_not_found": "Den angitte `.knxkeys`-filen ble ikke funnet i banen config/.storage/knx/", + "invalid_individual_address": "Verdien samsvarer ikke med m\u00f8nsteret for individuelle KNX-adresser.\n 'area.line.device'", + "invalid_ip_address": "Ugyldig IPv4-adresse.", + "invalid_signature": "Passordet for \u00e5 dekryptere `.knxkeys`-filen er feil." }, "step": { "manual_tunnel": { "data": { "host": "Vert", "individual_address": "Individuell adresse for tilkoblingen", - "local_ip": "Lokal IP for Home Assistant (la st\u00e5 tomt for automatisk gjenkjenning)", + "local_ip": "Lokal IP for hjemmeassistent", "port": "Port", "route_back": "Rute tilbake / NAT-modus", "tunneling_type": "KNX tunneltype" }, + "data_description": { + "host": "IP-adressen til KNX/IP-tunnelenheten.", + "local_ip": "La st\u00e5 tomt for \u00e5 bruke automatisk oppdagelse.", + "port": "Port p\u00e5 KNX/IP-tunnelenheten." + }, "description": "Vennligst skriv inn tilkoblingsinformasjonen til tunnelenheten din." }, "routing": { "data": { - "individual_address": "Individuell adresse for ruteforbindelsen", - "local_ip": "Lokal IP for Home Assistant (la st\u00e5 tomt for automatisk gjenkjenning)", - "multicast_group": "Multicast-gruppen som brukes til ruting", - "multicast_port": "Multicast-porten som brukes til ruting" + "individual_address": "Individuell adresse", + "local_ip": "Lokal IP for hjemmeassistent", + "multicast_group": "Multicast gruppe", + "multicast_port": "Multicast port" + }, + "data_description": { + "individual_address": "KNX-adresse som skal brukes av Home Assistant, f.eks. `0.0.4`", + "local_ip": "La st\u00e5 tomt for \u00e5 bruke automatisk oppdagelse." }, "description": "Vennligst konfigurer rutealternativene." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Filnavnet til `.knxkeys`-filen (inkludert utvidelse)", + "knxkeys_password": "Passordet for \u00e5 dekryptere `.knxkeys`-filen" + }, + "data_description": { + "knxkeys_filename": "Filen forventes \u00e5 bli funnet i konfigurasjonskatalogen din i `.storage/knx/`.\n I Home Assistant OS vil dette v\u00e6re `/config/.storage/knx/`\n Eksempel: `mitt_prosjekt.knxkeys`", + "knxkeys_password": "Dette ble satt ved eksport av filen fra ETS." + }, + "description": "Vennligst skriv inn informasjonen for `.knxkeys`-filen." + }, + "secure_manual": { + "data": { + "device_authentication": "Passord for enhetsgodkjenning", + "user_id": "bruker-ID", + "user_password": "Brukerpassord" + }, + "data_description": { + "device_authentication": "Dette settes i 'IP'-panelet til grensesnittet i ETS.", + "user_id": "Dette er ofte tunnelnummer +1. S\u00e5 'Tunnel 2' ville ha bruker-ID '3'.", + "user_password": "Passord for den spesifikke tunnelforbindelsen satt i 'Egenskaper'-panelet i tunnelen i ETS." + }, + "description": "Vennligst skriv inn din sikre IP-informasjon." + }, + "secure_tunneling": { + "description": "Velg hvordan du vil konfigurere KNX/IP Secure.", + "menu_options": { + "secure_knxkeys": "Bruk en `.knxkeys`-fil som inneholder IP-sikre n\u00f8kler", + "secure_manual": "Konfigurer IP-sikre n\u00f8kler manuelt" + } + }, "tunnel": { "data": { "gateway": "KNX Tunneltilkobling" @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX tilkoblingstype", "individual_address": "Standard individuell adresse", - "local_ip": "Lokal IP for Home Assistant (bruk 0.0.0.0 for automatisk deteksjon)", - "multicast_group": "Multicast-gruppe brukt til ruting og oppdagelse", - "multicast_port": "Multicast-port som brukes til ruting og oppdagelse", - "rate_limit": "Maksimalt utg\u00e5ende telegrammer per sekund", - "state_updater": "Aktiver lesetilstander globalt fra KNX-bussen" + "local_ip": "Lokal IP for hjemmeassistent", + "multicast_group": "Multicast gruppe", + "multicast_port": "Multicast port", + "rate_limit": "Satsgrense", + "state_updater": "Statens oppdatering" + }, + "data_description": { + "individual_address": "KNX-adresse som skal brukes av Home Assistant, f.eks. `0.0.4`", + "local_ip": "Bruk `0.0.0.0` for automatisk oppdagelse.", + "multicast_group": "Brukes til ruting og oppdagelse. Standard: `224.0.23.12`", + "multicast_port": "Brukes til ruting og oppdagelse. Standard: `3671`", + "rate_limit": "Maksimalt utg\u00e5ende telegrammer per sekund.\n Anbefalt: 20 til 40", + "state_updater": "Globalt aktiver eller deaktiver lesetilstander fra KNX-bussen. N\u00e5r den er deaktivert, vil ikke Home Assistant aktivt hente statuser fra KNX-bussen, \"sync_state\"-enhetsalternativer vil ikke ha noen effekt." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Rute tilbake / NAT-modus", "tunneling_type": "KNX tunneltype" + }, + "data_description": { + "host": "IP-adressen til KNX/IP-tunnelenheten.", + "port": "Port p\u00e5 KNX/IP-tunnelenheten." } } } diff --git a/homeassistant/components/knx/translations/pl.json b/homeassistant/components/knx/translations/pl.json index 8a30af3fd59..e0821090f29 100644 --- a/homeassistant/components/knx/translations/pl.json +++ b/homeassistant/components/knx/translations/pl.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "file_not_found": "Podany plik '.knxkeys' nie zosta\u0142 znaleziony w \u015bcie\u017cce config/.storage/knx/", + "invalid_individual_address": "Warto\u015b\u0107 nie pasuje do wzorca dla indywidualnego adresu KNX.\n 'obszar.linia.urz\u0105dzenie'", + "invalid_ip_address": "Nieprawid\u0142owy adres IPv4.", + "invalid_signature": "Has\u0142o do odszyfrowania pliku '.knxkeys' jest nieprawid\u0142owe." }, "step": { "manual_tunnel": { "data": { "host": "Nazwa hosta lub adres IP", "individual_address": "Indywidualny adres dla po\u0142\u0105czenia", - "local_ip": "Lokalny adres IP Home Assistant (pozostaw puste w celu automatycznego wykrywania)", + "local_ip": "Lokalny adres IP Home Assistanta", "port": "Port", "route_back": "Tryb Route Back / NAT", "tunneling_type": "Typ tunelowania KNX" }, + "data_description": { + "host": "Adres IP urz\u0105dzenia tuneluj\u0105cego KNX/IP.", + "local_ip": "Pozostaw puste, aby u\u017cy\u0107 automatycznego wykrywania.", + "port": "Port urz\u0105dzenia tuneluj\u0105cego KNX/IP." + }, "description": "Prosz\u0119 wprowadzi\u0107 informacje o po\u0142\u0105czeniu urz\u0105dzenia tuneluj\u0105cego." }, "routing": { "data": { - "individual_address": "Indywidualny adres dla po\u0142\u0105czenia routingowego", - "local_ip": "Lokalny adres IP Home Assistant (pozostaw puste w celu automatycznego wykrywania)", - "multicast_group": "Grupa multicast u\u017cyta do routingu", - "multicast_port": "Port multicast u\u017cyty do routingu" + "individual_address": "Adres indywidualny", + "local_ip": "Lokalny adres IP Home Assistanta", + "multicast_group": "Grupa multicast", + "multicast_port": "Port multicast" + }, + "data_description": { + "individual_address": "Adres KNX u\u017cywany przez Home Assistanta, np. `0.0.4`", + "local_ip": "Pozostaw puste, aby u\u017cy\u0107 automatycznego wykrywania." }, "description": "Prosz\u0119 skonfigurowa\u0107 opcje routingu." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "Nazwa pliku `.knxkeys` (wraz z rozszerzeniem)", + "knxkeys_password": "Has\u0142o do odszyfrowania pliku `.knxkeys`" + }, + "data_description": { + "knxkeys_filename": "Plik powinien znajdowa\u0107 si\u0119 w katalogu konfiguracyjnym w `.storage/knx/`.\nW systemie Home Assistant OS b\u0119dzie to `/config/.storage/knx/`\nPrzyk\u0142ad: `m\u00f3j_projekt.knxkeys`", + "knxkeys_password": "Zosta\u0142o to ustawione podczas eksportowania pliku z ETS." + }, + "description": "Wprowad\u017a informacje dotycz\u0105ce pliku `.knxkeys`." + }, + "secure_manual": { + "data": { + "device_authentication": "Has\u0142o uwierzytelniania urz\u0105dzenia", + "user_id": "Identyfikator u\u017cytkownika", + "user_password": "Has\u0142o u\u017cytkownika" + }, + "data_description": { + "device_authentication": "Jest to ustawiane w panelu \u201eIP\u201d interfejsu w ETS.", + "user_id": "Cz\u0119sto jest to numer tunelu plus 1. Tak wi\u0119c \u201eTunnel 2\u201d mia\u0142by identyfikator u\u017cytkownika \u201e3\u201d.", + "user_password": "Has\u0142o dla konkretnego po\u0142\u0105czenia tunelowego ustawione w panelu \u201eW\u0142a\u015bciwo\u015bci\u201d tunelu w ETS." + }, + "description": "Wprowad\u017a informacje o IP secure." + }, + "secure_tunneling": { + "description": "Wybierz, jak chcesz skonfigurowa\u0107 KNX/IP secure.", + "menu_options": { + "secure_knxkeys": "U\u017cyj pliku `.knxkeys` zawieraj\u0105cego klucze IP secure", + "secure_manual": "R\u0119czna konfiguracja kluczy IP secure" + } + }, "tunnel": { "data": { "gateway": "Po\u0142\u0105czenie tunelowe KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "Typ po\u0142\u0105czenia KNX", "individual_address": "Domy\u015blny adres indywidualny", - "local_ip": "Lokalny adres IP Home Assistant (u\u017cyj 0.0.0.0 w celu automatycznego wykrywania)", - "multicast_group": "Grupa multicast u\u017cywana do routingu i wykrywania", - "multicast_port": "Port multicast u\u017cywany do routingu i wykrywania", - "rate_limit": "Maksymalna liczba wychodz\u0105cych wiadomo\u015bci na sekund\u0119", - "state_updater": "Zezw\u00f3l globalnie na odczyt stan\u00f3w z magistrali KNX" + "local_ip": "Lokalny adres IP Home Assistanta", + "multicast_group": "Grupa multicast", + "multicast_port": "Port multicast", + "rate_limit": "Limit", + "state_updater": "Aktualizator stanu" + }, + "data_description": { + "individual_address": "Adres KNX u\u017cywany przez Home Assistanta, np. `0.0.4`", + "local_ip": "U\u017cyj `0.0.0.0` do automatycznego wykrywania.", + "multicast_group": "U\u017cywany do routingu i wykrywania. Domy\u015blnie: `224.0.23.12`", + "multicast_port": "U\u017cywany do routingu i wykrywania. Domy\u015blnie: `3671`", + "rate_limit": "Maksymalna liczba wychodz\u0105cych wiadomo\u015bci na sekund\u0119.\nZalecane: od 20 do 40", + "state_updater": "Globalnie w\u0142\u0105czaj lub wy\u0142\u0105czaj odczytywanie stan\u00f3w z magistrali KNX. Po wy\u0142\u0105czeniu, Home Assistant nie b\u0119dzie aktywnie pobiera\u0107 stan\u00f3w z magistrali KNX, opcje encji `sync_state` nie b\u0119d\u0105 mia\u0142y \u017cadnego efektu." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Tryb Route Back / NAT", "tunneling_type": "Typ tunelowania KNX" + }, + "data_description": { + "host": "Adres IP urz\u0105dzenia tuneluj\u0105cego KNX/IP.", + "port": "Port urz\u0105dzenia tuneluj\u0105cego KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/pt-BR.json b/homeassistant/components/knx/translations/pt-BR.json index 0e8e3402961..950cedc0721 100644 --- a/homeassistant/components/knx/translations/pt-BR.json +++ b/homeassistant/components/knx/translations/pt-BR.json @@ -5,29 +5,73 @@ "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "error": { - "cannot_connect": "Falha ao conectar" + "cannot_connect": "Falha ao conectar", + "file_not_found": "O arquivo `.knxkeys` especificado n\u00e3o foi encontrado no caminho config/.storage/knx/", + "invalid_individual_address": "O valor n\u00e3o corresponde ao padr\u00e3o do endere\u00e7o individual KNX.\n '\u00e1rea.linha.dispositivo'", + "invalid_ip_address": "Endere\u00e7o IPv4 inv\u00e1lido.", + "invalid_signature": "A senha para descriptografar o arquivo `.knxkeys` est\u00e1 errada." }, "step": { "manual_tunnel": { "data": { "host": "Nome do host", "individual_address": "Endere\u00e7o individual para a conex\u00e3o", - "local_ip": "IP local do Home Assistant (deixe em branco para detec\u00e7\u00e3o autom\u00e1tica)", + "local_ip": "IP local do Home Assistant", "port": "Porta", "route_back": "Modo Rota de Retorno / NAT", "tunneling_type": "Tipo de t\u00fanel KNX" }, + "data_description": { + "host": "Endere\u00e7o IP do dispositivo de tunelamento KNX/IP.", + "local_ip": "Deixe em branco para usar a descoberta autom\u00e1tica.", + "port": "Porta do dispositivo de tunelamento KNX/IP." + }, "description": "Por favor, digite as informa\u00e7\u00f5es de conex\u00e3o do seu dispositivo de tunelamento." }, "routing": { "data": { - "individual_address": "Endere\u00e7o individual para a conex\u00e3o de roteamento", - "local_ip": "IP local do Home Assistant (deixe vazio para detec\u00e7\u00e3o autom\u00e1tica)", - "multicast_group": "O grupo multicast usado para roteamento", - "multicast_port": "A porta multicast usada para roteamento" + "individual_address": "Endere\u00e7o individual", + "local_ip": "IP local do Home Assistant", + "multicast_group": "Grupo multicast", + "multicast_port": "Porta multicast" + }, + "data_description": { + "individual_address": "Endere\u00e7o KNX a ser usado pelo Home Assistant, por exemplo, `0.0.4`", + "local_ip": "Deixe em branco para usar a descoberta autom\u00e1tica." }, "description": "Por favor, configure as op\u00e7\u00f5es de roteamento." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "O nome do seu arquivo `.knxkeys` (incluindo extens\u00e3o)", + "knxkeys_password": "A senha para descriptografar o arquivo `.knxkeys`" + }, + "data_description": { + "knxkeys_filename": "Espera-se que o arquivo seja encontrado em seu diret\u00f3rio de configura\u00e7\u00e3o em `.storage/knx/`.\n No sistema operacional Home Assistant seria `/config/.storage/knx/`\n Exemplo: `my_project.knxkeys`", + "knxkeys_password": "Isso foi definido ao exportar o arquivo do ETS." + }, + "description": "Por favor, insira as informa\u00e7\u00f5es para o seu arquivo `.knxkeys`." + }, + "secure_manual": { + "data": { + "device_authentication": "Senha de autentica\u00e7\u00e3o do dispositivo", + "user_id": "ID do usu\u00e1rio", + "user_password": "Senha do usu\u00e1rio" + }, + "data_description": { + "device_authentication": "Isso \u00e9 definido no painel 'IP' da interface no ETS.", + "user_id": "Isso geralmente \u00e9 o n\u00famero do t\u00fanel +1. Portanto, 'T\u00fanel 2' teria o ID de usu\u00e1rio '3'.", + "user_password": "Senha para a conex\u00e3o de t\u00fanel espec\u00edfica definida no painel 'Propriedades' do t\u00fanel no ETS." + }, + "description": "Por favor, insira suas informa\u00e7\u00f5es seguras de IP." + }, + "secure_tunneling": { + "description": "Selecione como deseja configurar o KNX/IP Secure.", + "menu_options": { + "secure_knxkeys": "Use um arquivo `.knxkeys` contendo chaves seguras de IP", + "secure_manual": "Configurar manualmente as chaves de seguran\u00e7a IP" + } + }, "tunnel": { "data": { "gateway": "Conex\u00e3o do t\u00fanel KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "Tipo de conex\u00e3o KNX", "individual_address": "Endere\u00e7o individual padr\u00e3o", - "local_ip": "IP local do Home Assistant (use 0.0.0.0 para detec\u00e7\u00e3o autom\u00e1tica)", - "multicast_group": "Grupo multicast usado para roteamento e descoberta", - "multicast_port": "Porta multicast usada para roteamento e descoberta", - "rate_limit": "M\u00e1ximo de telegramas de sa\u00edda por segundo", - "state_updater": "Permitir globalmente estados de leitura a partir do KNX Bus" + "local_ip": "IP local do Home Assistant", + "multicast_group": "Grupo multicast", + "multicast_port": "Porta multicast", + "rate_limit": "Taxa limite", + "state_updater": "Atualizador de estado" + }, + "data_description": { + "individual_address": "Endere\u00e7o KNX a ser usado pelo Home Assistant, por exemplo, `0.0.4`", + "local_ip": "Use `0.0.0.0` para descoberta autom\u00e1tica.", + "multicast_group": "Usado para roteamento e descoberta. Padr\u00e3o: `224.0.23.12`", + "multicast_port": "Usado para roteamento e descoberta. Padr\u00e3o: `3671`", + "rate_limit": "M\u00e1ximo de telegramas de sa\u00edda por segundo.\n Recomendado: 20 a 40", + "state_updater": "Habilite ou desabilite globalmente os estados de leitura do barramento KNX. Quando desativado, o Home Assistant n\u00e3o recuperar\u00e1 ativamente os estados do barramento KNX, as op\u00e7\u00f5es de entidade `sync_state` n\u00e3o ter\u00e3o efeito." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Porta", "route_back": "Modo Rota de Retorno / NAT", "tunneling_type": "Tipo de t\u00fanel KNX" + }, + "data_description": { + "host": "Endere\u00e7o IP do dispositivo de tunelamento KNX/IP.", + "port": "Porta do dispositivo de tunelamento KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/ru.json b/homeassistant/components/knx/translations/ru.json index 6c1a41dac91..14b9f919aa2 100644 --- a/homeassistant/components/knx/translations/ru.json +++ b/homeassistant/components/knx/translations/ru.json @@ -5,29 +5,73 @@ "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "file_not_found": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b `.knxkeys` \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0432 config/.storage/knx/", + "invalid_individual_address": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043b\u043e\u043d\u0443 \u0434\u043b\u044f \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0430\u0434\u0440\u0435\u0441\u0430 KNX 'area.line.device'.", + "invalid_ip_address": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 IPv4.", + "invalid_signature": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 `.knxkeys`." }, "step": { "manual_tunnel": { "data": { "host": "\u0425\u043e\u0441\u0442", "individual_address": "\u0418\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", - "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f)", + "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant", "port": "\u041f\u043e\u0440\u0442", "route_back": "\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 / \u0440\u0435\u0436\u0438\u043c NAT", "tunneling_type": "\u0422\u0438\u043f \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX" }, + "data_description": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX/IP.", + "local_ip": "\u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435.", + "port": "\u041f\u043e\u0440\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX/IP." + }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438." }, "routing": { "data": { - "individual_address": "\u0418\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f", - "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f)", - "multicast_group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438", - "multicast_port": "\u041f\u043e\u0440\u0442 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438" + "individual_address": "\u0418\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441", + "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant", + "multicast_group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438", + "multicast_port": "\u041f\u043e\u0440\u0442 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438" + }, + "data_description": { + "individual_address": "\u0410\u0434\u0440\u0435\u0441 KNX, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f Home Assistant, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, `0.0.4`", + "local_ip": "\u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435." }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "\u0418\u043c\u044f \u0444\u0430\u0439\u043b\u0430 `.knxkeys` (\u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435)", + "knxkeys_password": "\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 `.knxkeys`" + }, + "data_description": { + "knxkeys_filename": "\u041e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0444\u0430\u0439\u043b \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0439\u0434\u0435\u043d \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 `.storage/knx/`.\n\u0415\u0441\u043b\u0438 \u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 Home Assistant OS \u044d\u0442\u043e\u0442 \u043f\u0443\u0442\u044c \u0431\u0443\u0434\u0435\u0442 `/config/.storage/knx/`\n\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: `my_project.knxkeys`", + "knxkeys_password": "\u042d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0431\u044b\u043b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043f\u0440\u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435 \u0444\u0430\u0439\u043b\u0430 \u0438\u0437 ETS." + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0444\u0430\u0439\u043b\u0435 `.knxkeys`." + }, + "secure_manual": { + "data": { + "device_authentication": "\u041f\u0430\u0440\u043e\u043b\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "user_id": "ID \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", + "user_password": "\u041f\u0430\u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "data_description": { + "device_authentication": "\u042d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 'IP' \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 ETS.", + "user_id": "\u0427\u0430\u0441\u0442\u043e \u043d\u043e\u043c\u0435\u0440 \u0442\u0443\u043d\u043d\u0435\u043b\u044f +1. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, 'Tunnel 2' \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u0435\u0442\u044c User-ID '3'.", + "user_password": "\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0442\u0443\u043d\u043d\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f, \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0439 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 'Properties' \u0442\u0443\u043d\u043d\u0435\u043b\u044f \u0432 ETS." + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u043e IP Secure." + }, + "secure_tunneling": { + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 KNX/IP Secure.", + "menu_options": { + "secure_knxkeys": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b `.knxkeys`, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439 \u043a\u043b\u044e\u0447\u0438 IP secure", + "secure_manual": "\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u043b\u044e\u0447\u0438 IP Secure \u0432\u0440\u0443\u0447\u043d\u0443\u044e" + } + }, "tunnel": { "data": { "gateway": "\u0422\u0443\u043d\u043d\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f KNX" @@ -48,11 +92,19 @@ "data": { "connection_type": "\u0422\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f KNX", "individual_address": "\u0418\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", - "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant (0.0.0.0 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f)", - "multicast_group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f", - "multicast_port": "\u041f\u043e\u0440\u0442 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f", - "rate_limit": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0443", - "state_updater": "\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0441 \u0448\u0438\u043d\u044b KNX" + "local_ip": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 Home Assistant", + "multicast_group": "\u0413\u0440\u0443\u043f\u043f\u0430 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438", + "multicast_port": "\u041f\u043e\u0440\u0442 \u043c\u043d\u043e\u0433\u043e\u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438", + "rate_limit": "\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438", + "state_updater": "\u0421\u0440\u0435\u0434\u0441\u0442\u0432\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f" + }, + "data_description": { + "individual_address": "\u0410\u0434\u0440\u0435\u0441 KNX, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f Home Assistant, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, `0.0.4`", + "local_ip": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 `0.0.0.0` \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f.", + "multicast_group": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `224.0.23.12`", + "multicast_port": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `3671`", + "rate_limit": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0443.\n\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f: \u043e\u0442 20 \u0434\u043e 40", + "state_updater": "\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438\u0437 \u0448\u0438\u043d\u044b KNX. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d, Home Assistant \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438\u0437 \u0448\u0438\u043d\u044b KNX, \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0431\u044a\u0435\u043a\u0442\u0430 sync_state \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0430." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "\u041f\u043e\u0440\u0442", "route_back": "\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 / \u0440\u0435\u0436\u0438\u043c NAT", "tunneling_type": "\u0422\u0438\u043f \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX" + }, + "data_description": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX/IP.", + "port": "\u041f\u043e\u0440\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0442\u0443\u043d\u043d\u0435\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f KNX/IP." } } } diff --git a/homeassistant/components/knx/translations/tr.json b/homeassistant/components/knx/translations/tr.json index 6267038a6e0..c10f35ca48f 100644 --- a/homeassistant/components/knx/translations/tr.json +++ b/homeassistant/components/knx/translations/tr.json @@ -5,29 +5,73 @@ "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, "error": { - "cannot_connect": "Ba\u011flanma hatas\u0131" + "cannot_connect": "Ba\u011flanma hatas\u0131", + "file_not_found": "Belirtilen `.knxkeys` dosyas\u0131 config/.storage/knx/ yolunda bulunamad\u0131", + "invalid_individual_address": "De\u011fer, KNX bireysel adresi i\u00e7in modelle e\u015fle\u015fmiyor.\n 'alan.hat.cihaz'", + "invalid_ip_address": "Ge\u00e7ersiz IPv4 adresi.", + "invalid_signature": "`.knxkeys` dosyas\u0131n\u0131n \u015fifresini \u00e7\u00f6zmek i\u00e7in \u015fifre yanl\u0131\u015f." }, "step": { "manual_tunnel": { "data": { "host": "Sunucu", "individual_address": "Ba\u011flant\u0131 i\u00e7in bireysel adres", - "local_ip": "Home Assistant Yerel IP'si (otomatik alg\u0131lama i\u00e7in bo\u015f b\u0131rak\u0131n)", + "local_ip": "Home Asistan\u0131n\u0131n Yerel IP'si", "port": "Port", "route_back": "Geri Y\u00f6nlendirme / NAT Modu", "tunneling_type": "KNX T\u00fcnel Tipi" }, + "data_description": { + "host": "KNX/IP t\u00fcnelleme cihaz\u0131n\u0131n IP adresi.", + "local_ip": "Otomatik bulmay\u0131 kullanmak i\u00e7in bo\u015f b\u0131rak\u0131n.", + "port": "KNX/IP t\u00fcnelleme cihaz\u0131n\u0131n ba\u011flant\u0131 noktas\u0131." + }, "description": "L\u00fctfen t\u00fcnel cihaz\u0131n\u0131z\u0131n ba\u011flant\u0131 bilgilerini girin." }, "routing": { "data": { - "individual_address": "Y\u00f6nlendirme ba\u011flant\u0131s\u0131 i\u00e7in bireysel adres", - "local_ip": "Home Assistant Yerel IP'si (otomatik alg\u0131lama i\u00e7in bo\u015f b\u0131rak\u0131n)", - "multicast_group": "Y\u00f6nlendirme i\u00e7in kullan\u0131lan \u00e7ok noktaya yay\u0131n grubu", - "multicast_port": "Y\u00f6nlendirme i\u00e7in kullan\u0131lan \u00e7ok noktaya yay\u0131n ba\u011flant\u0131 noktas\u0131" + "individual_address": "Bireysel adres", + "local_ip": "Home Asistan\u0131n\u0131n Yerel IP'si", + "multicast_group": "\u00c7ok noktaya yay\u0131n grubu", + "multicast_port": "\u00c7ok noktaya yay\u0131n ba\u011flant\u0131 noktas\u0131" + }, + "data_description": { + "individual_address": "Home Assistant taraf\u0131ndan kullan\u0131lacak KNX adresi, \u00f6r. \"0.0.4\"", + "local_ip": "Otomatik bulmay\u0131 kullanmak i\u00e7in bo\u015f b\u0131rak\u0131n." }, "description": "L\u00fctfen y\u00f6nlendirme se\u00e7eneklerini yap\u0131land\u0131r\u0131n." }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "`.knxkeys` dosyan\u0131z\u0131n dosya ad\u0131 (uzant\u0131 dahil)", + "knxkeys_password": "`.knxkeys` dosyas\u0131n\u0131n \u015fifresini \u00e7\u00f6zmek i\u00e7in \u015fifre" + }, + "data_description": { + "knxkeys_filename": "Dosyan\u0131n yap\u0131land\u0131rma dizininizde `.storage/knx/` i\u00e7inde bulunmas\u0131 bekleniyor.\n Home Assistant OS'de bu, `/config/.storage/knx/` olacakt\u0131r.\n \u00d6rnek: \"my_project.knxkeys\"", + "knxkeys_password": "Bu, dosyay\u0131 ETS'den d\u0131\u015fa aktar\u0131rken ayarland\u0131." + }, + "description": "L\u00fctfen `.knxkeys` dosyan\u0131z i\u00e7in bilgileri girin." + }, + "secure_manual": { + "data": { + "device_authentication": "Cihaz do\u011frulama \u015fifresi", + "user_id": "Kullan\u0131c\u0131 Kimli\u011fi", + "user_password": "Kullan\u0131c\u0131 \u015fifresi" + }, + "data_description": { + "device_authentication": "Bu, ETS'deki aray\u00fcz\u00fcn 'IP' panelinde ayarlan\u0131r.", + "user_id": "Bu genellikle t\u00fcnel numaras\u0131 +1'dir. Yani 'T\u00fcnel 2' Kullan\u0131c\u0131 Kimli\u011fi '3' olacakt\u0131r.", + "user_password": "ETS'de t\u00fcnelin '\u00d6zellikler' panelinde ayarlanan belirli t\u00fcnel ba\u011flant\u0131s\u0131 i\u00e7in \u015fifre." + }, + "description": "L\u00fctfen IP g\u00fcvenli bilgilerinizi giriniz." + }, + "secure_tunneling": { + "description": "KNX/IP Secure'u nas\u0131l yap\u0131land\u0131rmak istedi\u011finizi se\u00e7in.", + "menu_options": { + "secure_knxkeys": "IP g\u00fcvenli anahtarlar\u0131 i\u00e7eren bir \".knxkeys\" dosyas\u0131 kullan\u0131n", + "secure_manual": "IP g\u00fcvenli anahtarlar\u0131n\u0131 manuel olarak yap\u0131land\u0131r\u0131n" + } + }, "tunnel": { "data": { "gateway": "KNX T\u00fcnel Ba\u011flant\u0131s\u0131" @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX Ba\u011flant\u0131 T\u00fcr\u00fc", "individual_address": "Varsay\u0131lan bireysel adres", - "local_ip": "Home Assistant Yerel IP'si (otomatik alg\u0131lama i\u00e7in 0.0.0.0 kullan\u0131n)", - "multicast_group": "Y\u00f6nlendirme ve ke\u015fif i\u00e7in kullan\u0131lan \u00e7ok noktaya yay\u0131n grubu", - "multicast_port": "Y\u00f6nlendirme ve ke\u015fif i\u00e7in kullan\u0131lan \u00e7ok noktaya yay\u0131n ba\u011flant\u0131 noktas\u0131", - "rate_limit": "Saniyede maksimum giden telegram say\u0131s\u0131", - "state_updater": "KNX Veri Yolu'ndan okuma durumlar\u0131n\u0131 genel olarak etkinle\u015ftirin" + "local_ip": "Home Asistan\u0131n\u0131n Yerel IP'si", + "multicast_group": "\u00c7ok noktaya yay\u0131n grubu", + "multicast_port": "\u00c7ok noktaya yay\u0131n ba\u011flant\u0131 noktas\u0131", + "rate_limit": "H\u0131z s\u0131n\u0131r\u0131", + "state_updater": "Durum g\u00fcncelleyici" + }, + "data_description": { + "individual_address": "Home Assistant taraf\u0131ndan kullan\u0131lacak KNX adresi, \u00f6r. \"0.0.4\"", + "local_ip": "Otomatik ke\u015fif i\u00e7in \"0.0.0.0\"\u0131 kullan\u0131n.", + "multicast_group": "Y\u00f6nlendirme ve ke\u015fif i\u00e7in kullan\u0131l\u0131r. Varsay\u0131lan: \"224.0.23.12\"", + "multicast_port": "Y\u00f6nlendirme ve ke\u015fif i\u00e7in kullan\u0131l\u0131r. Varsay\u0131lan: \"3671\"", + "rate_limit": "Saniyede maksimum giden telegram say\u0131s\u0131.\n \u00d6nerilen: 20 ila 40", + "state_updater": "KNX Bus'tan okuma durumlar\u0131n\u0131 k\u00fcresel olarak etkinle\u015ftirin veya devre d\u0131\u015f\u0131 b\u0131rak\u0131n. Devre d\u0131\u015f\u0131 b\u0131rak\u0131ld\u0131\u011f\u0131nda, Home Assistant KNX Bus'tan durumlar\u0131 aktif olarak almaz, 'sync_state' varl\u0131k se\u00e7eneklerinin hi\u00e7bir etkisi olmaz." } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "Port", "route_back": "Geri Y\u00f6nlendirme / NAT Modu", "tunneling_type": "KNX T\u00fcnel Tipi" + }, + "data_description": { + "host": "KNX/IP t\u00fcnelleme cihaz\u0131n\u0131n IP adresi.", + "port": "KNX/IP t\u00fcnelleme cihaz\u0131n\u0131n ba\u011flant\u0131 noktas\u0131." } } } diff --git a/homeassistant/components/knx/translations/zh-Hant.json b/homeassistant/components/knx/translations/zh-Hant.json index b6c09456fb3..8f740b85f6d 100644 --- a/homeassistant/components/knx/translations/zh-Hant.json +++ b/homeassistant/components/knx/translations/zh-Hant.json @@ -5,29 +5,73 @@ "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "file_not_found": "\u8def\u5f91 config/.storage/knx/ \u5167\u627e\u4e0d\u5230\u6307\u5b9a `.knxkeys` \u6a94\u6848", + "invalid_individual_address": "\u6578\u503c\u8207 KNX \u500b\u5225\u4f4d\u5740\u4e0d\u76f8\u7b26\u3002\n'area.line.device'", + "invalid_ip_address": "IPv4 \u4f4d\u5740\u7121\u6548\u3002", + "invalid_signature": "\u52a0\u5bc6 `.knxkeys` \u6a94\u6848\u5bc6\u78bc\u932f\u8aa4\u3002" }, "step": { "manual_tunnel": { "data": { "host": "\u4e3b\u6a5f\u7aef", "individual_address": "\u9023\u7dda\u500b\u5225\u4f4d\u5740", - "local_ip": "Home Assistant \u672c\u5730\u7aef IP\uff08\u4fdd\u7559\u7a7a\u767d\u4ee5\u81ea\u52d5\u5075\u6e2c\uff09", + "local_ip": "Home Assistant \u672c\u5730\u7aef IP", "port": "\u901a\u8a0a\u57e0", "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f", "tunneling_type": "KNX \u901a\u9053\u985e\u5225" }, + "data_description": { + "host": "KNX/IP \u901a\u9053\u88dd\u7f6e IP \u4f4d\u5740\u3002", + "local_ip": "\u4fdd\u6301\u7a7a\u767d\u4ee5\u4f7f\u7528\u81ea\u52d5\u641c\u7d22\u3002", + "port": "KNX/IP \u901a\u9053\u88dd\u7f6e\u901a\u8a0a\u57e0\u3002" + }, "description": "\u8acb\u8f38\u5165\u901a\u9053\u88dd\u7f6e\u7684\u9023\u7dda\u8cc7\u8a0a\u3002" }, "routing": { "data": { - "individual_address": "\u8def\u7531\u9023\u7dda\u500b\u5225\u4f4d\u5740", - "local_ip": "Home Assistant \u672c\u5730\u7aef IP\uff08\u4fdd\u7559\u7a7a\u767d\u4ee5\u81ea\u52d5\u5075\u6e2c\uff09", - "multicast_group": "\u4f7f\u7528\u65bc\u8def\u7531\u7684 Multicast \u7fa4\u7d44", - "multicast_port": "\u4f7f\u7528\u65bc\u8def\u7531\u7684 Multicast \u901a\u8a0a\u57e0" + "individual_address": "\u500b\u5225\u4f4d\u5740", + "local_ip": "Home Assistant \u672c\u5730\u7aef IP", + "multicast_group": "Multicast \u7fa4\u7d44", + "multicast_port": "Multicast \u901a\u8a0a\u57e0" + }, + "data_description": { + "individual_address": "Home Assistant \u6240\u4f7f\u7528\u4e4b KNX \u4f4d\u5740\u3002\u4f8b\u5982\uff1a`0.0.4`", + "local_ip": "\u4fdd\u6301\u7a7a\u767d\u4ee5\u4f7f\u7528\u81ea\u52d5\u641c\u7d22\u3002" }, "description": "\u8acb\u8a2d\u5b9a\u8def\u7531\u9078\u9805\u3002" }, + "secure_knxkeys": { + "data": { + "knxkeys_filename": "`.knxkeys` \u6a94\u6848\u5168\u540d\uff08\u5305\u542b\u526f\u6a94\u540d\uff09", + "knxkeys_password": "\u52a0\u5bc6 `.knxkeys` \u6a94\u6848\u5bc6\u78bc" + }, + "data_description": { + "knxkeys_filename": "\u6a94\u6848\u61c9\u8a72\u4f4d\u65bc\u8a2d\u5b9a\u8cc7\u6599\u593e `.storage/knx/` \u5167\u3002\n\u82e5\u70ba Home Assistant OS\u3001\u5247\u61c9\u8a72\u70ba `/config/.storage/knx/`\n\u4f8b\u5982\uff1a`my_project.knxkeys`", + "knxkeys_password": "\u81ea ETS \u532f\u51fa\u6a94\u6848\u4e2d\u9032\u884c\u8a2d\u5b9a\u3002" + }, + "description": "\u8acb\u8f38\u5165 `.knxkeys` \u6a94\u6848\u8cc7\u8a0a\u3002" + }, + "secure_manual": { + "data": { + "device_authentication": "\u88dd\u7f6e\u8a8d\u8b49\u5bc6\u78bc", + "user_id": "\u4f7f\u7528\u8005 ID", + "user_password": "\u4f7f\u7528\u8005\u5bc6\u78bc" + }, + "data_description": { + "device_authentication": "\u65bc EST \u4ecb\u9762\u4e2d 'IP' \u9762\u677f\u9032\u884c\u8a2d\u5b9a\u3002", + "user_id": "\u901a\u5e38\u70ba\u901a\u9053\u6578 +1\u3002\u56e0\u6b64 'Tunnel 2' \u5c07\u5177\u6709\u4f7f\u7528\u8005 ID '3'\u3002", + "user_password": "\u65bc ETS \u901a\u9053 'Properties' \u9762\u677f\u53ef\u8a2d\u5b9a\u6307\u5b9a\u901a\u9053\u9023\u7dda\u5bc6\u78bc\u3002" + }, + "description": "\u8acb\u8f38\u5165 IP \u52a0\u5bc6\u8cc7\u8a0a\u3002" + }, + "secure_tunneling": { + "description": "\u9078\u64c7\u5982\u4f55\u8a2d\u5b9a KNX/IP \u52a0\u5bc6\u3002", + "menu_options": { + "secure_knxkeys": "\u4f7f\u7528\u5305\u542b IP \u52a0\u5bc6\u91d1\u8000\u7684 knxkeys \u6a94\u6848", + "secure_manual": "\u624b\u52d5\u8a2d\u5b9a IP \u52a0\u5bc6\u91d1\u8000" + } + }, "tunnel": { "data": { "gateway": "KNX \u901a\u9053\u9023\u7dda" @@ -48,11 +92,19 @@ "data": { "connection_type": "KNX \u9023\u7dda\u985e\u5225", "individual_address": "\u9810\u8a2d\u500b\u5225\u4f4d\u5740", - "local_ip": "Home Assistant \u672c\u5730\u7aef IP\uff08\u586b\u5165 0.0.0.0 \u555f\u7528\u81ea\u52d5\u5075\u6e2c\uff09", - "multicast_group": "\u4f7f\u7528\u65bc\u8def\u7531\u8207\u641c\u7d22\u7684 Multicast \u7fa4\u7d44", - "multicast_port": "\u4f7f\u7528\u65bc\u8def\u7531\u8207\u641c\u7d22\u7684 Multicast \u901a\u8a0a\u57e0", - "rate_limit": "\u6700\u5927\u6bcf\u79d2\u767c\u51fa Telegram", - "state_updater": "\u7531 KNX Bus \u8b80\u53d6\u72c0\u614b\u5168\u555f\u7528" + "local_ip": "Home Assistant \u672c\u5730\u7aef IP", + "multicast_group": "Multicast \u7fa4\u7d44", + "multicast_port": "Multicast \u901a\u8a0a\u57e0", + "rate_limit": "\u983b\u7387\u9650\u5236", + "state_updater": "\u88dd\u614b\u66f4\u65b0\u5668" + }, + "data_description": { + "individual_address": "Home Assistant \u6240\u4f7f\u7528\u4e4b KNX \u4f4d\u5740\u3002\u4f8b\u5982\uff1a`0.0.4`", + "local_ip": "\u4f7f\u7528 `0.0.0.0` \u9032\u884c\u81ea\u52d5\u641c\u7d22\u3002", + "multicast_group": "\u4f7f\u7528\u65bc\u8def\u7531\u8207\u81ea\u52d5\u641c\u7d22\u3002\u9810\u8a2d\u503c\uff1a`224.0.23.12`", + "multicast_port": "\u4f7f\u7528\u65bc\u8def\u7531\u8207\u81ea\u52d5\u641c\u7d22\u3002\u9810\u8a2d\u503c\uff1a`3671`", + "rate_limit": "\u6bcf\u79d2\u6700\u5927 Telegram \u767c\u9001\u91cf\u3002\u5efa\u8b70\uff1a20 - 40", + "state_updater": "\u5168\u5c40\u958b\u555f\u6216\u95dc\u9589\u81ea KNX Bus \u8b80\u53d6\u72c0\u614b\u3002\u7576\u95dc\u9589\u6642\u3001Home Assistant \u5c07\u4e0d\u6703\u4e3b\u52d5\u5f9e KNX Bus \u7372\u53d6\u72c0\u614b\uff0c`sync_state` \u5be6\u9ad4\u9078\u9805\u5c07\u4e0d\u5177\u6548\u679c\u3002" } }, "tunnel": { @@ -62,6 +114,10 @@ "port": "\u901a\u8a0a\u57e0", "route_back": "\u8def\u7531\u8fd4\u56de / NAT \u6a21\u5f0f", "tunneling_type": "KNX \u901a\u9053\u985e\u5225" + }, + "data_description": { + "host": "KNX/IP \u901a\u9053\u88dd\u7f6e IP \u4f4d\u5740\u3002", + "port": "KNX/IP \u901a\u9053\u88dd\u7f6e\u901a\u8a0a\u57e0\u3002" } } } diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 51e242ae137..0e254a7d209 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -14,7 +14,11 @@ from pykodi import CannotConnectError import voluptuous as vol from homeassistant.components import media_source -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) @@ -31,20 +35,6 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_TVSHOW, MEDIA_TYPE_URL, MEDIA_TYPE_VIDEO, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -142,23 +132,6 @@ MAP_KODI_MEDIA_TYPES = { MEDIA_TYPE_TVSHOW: "tvshowid", } -SUPPORT_KODI = ( - SUPPORT_BROWSE_MEDIA - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_SEEK - | SUPPORT_SHUFFLE_SET - | SUPPORT_STOP - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -300,6 +273,23 @@ def cmd(func): class KodiEntity(MediaPlayerEntity): """Representation of a XBMC/Kodi device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + ) + def __init__(self, connection, kodi, name, uid): """Initialize the Kodi entity.""" self._connection = connection @@ -647,11 +637,6 @@ class KodiEntity(MediaPlayerEntity): return None - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_KODI - async def async_turn_on(self): """Turn the media player on.""" _LOGGER.debug("Firing event to turn on device") diff --git a/homeassistant/components/konnected/translations/fr.json b/homeassistant/components/konnected/translations/fr.json index 2ddd335fa9e..4b84efa351d 100644 --- a/homeassistant/components/konnected/translations/fr.json +++ b/homeassistant/components/konnected/translations/fr.json @@ -97,7 +97,7 @@ "options_switch": { "data": { "activation": "Sortie lorsque activ\u00e9", - "momentary": "Dur\u00e9e de l'impulsion (ms) (facultatif)", + "momentary": "Dur\u00e9e de l'impulsion (en millisecondes, facultatif)", "more_states": "Configurer des \u00e9tats suppl\u00e9mentaires pour cette zone", "name": "Nom (facultatif)", "pause": "Pause entre les impulsions (ms) (facultatif)", diff --git a/homeassistant/components/konnected/translations/hu.json b/homeassistant/components/konnected/translations/hu.json index e634727593b..c40b1823424 100644 --- a/homeassistant/components/konnected/translations/hu.json +++ b/homeassistant/components/konnected/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "not_konn_panel": "Nem felismert Konnected.io eszk\u00f6z", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" @@ -41,7 +41,7 @@ "options_binary": { "data": { "inverse": "Invert\u00e1lja a nyitott/z\u00e1rt \u00e1llapotot", - "name": "N\u00e9v (nem k\u00f6telez\u0151)", + "name": "Elnevez\u00e9s (nem k\u00f6telez\u0151)", "type": "Bin\u00e1ris \u00e9rz\u00e9kel\u0151 t\u00edpusa" }, "description": "{zone} opci\u00f3k", @@ -49,7 +49,7 @@ }, "options_digital": { "data": { - "name": "N\u00e9v (nem k\u00f6telez\u0151)", + "name": "Elnevez\u00e9s (nem k\u00f6telez\u0151)", "poll_interval": "Lek\u00e9rdez\u00e9si id\u0151k\u00f6z (perc) (opcion\u00e1lis)", "type": "\u00c9rz\u00e9kel\u0151 t\u00edpusa" }, @@ -99,7 +99,7 @@ "activation": "Kimenet bekapcsolt \u00e1llapotban", "momentary": "Impulzus id\u0151tartama (ms) (opcion\u00e1lis)", "more_states": "Tov\u00e1bbi \u00e1llapotok konfigur\u00e1l\u00e1sa ehhez a z\u00f3n\u00e1hoz", - "name": "N\u00e9v (nem k\u00f6telez\u0151)", + "name": "Elnevez\u00e9s (nem k\u00f6telez\u0151)", "pause": "Sz\u00fcnet impulzusok k\u00f6z\u00f6tt (ms) (opcion\u00e1lis)", "repeat": "Ism\u00e9tl\u00e9si id\u0151k (-1 = v\u00e9gtelen) (opcion\u00e1lis)" }, diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index 81127f0dff2..682a27b50d0 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -34,8 +34,8 @@ }, "error": { "bad_host": "URL host API di sostituzione non valido", - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "step": { "options_binary": { @@ -92,7 +92,7 @@ "override_api_host": "Sovrascrivi l'URL predefinito del pannello host API di Home Assistant" }, "description": "Seleziona il comportamento desiderato per il tuo pannello", - "title": "Configura Altro" + "title": "Configura altro" }, "options_switch": { "data": { diff --git a/homeassistant/components/konnected/translations/pt-BR.json b/homeassistant/components/konnected/translations/pt-BR.json index b49b487ab0d..e3079fc50f7 100644 --- a/homeassistant/components/konnected/translations/pt-BR.json +++ b/homeassistant/components/konnected/translations/pt-BR.json @@ -102,7 +102,7 @@ "repeat": "Intervalo para repetir (-1=infinito) (opcional)" }, "description": "Selecione as op\u00e7\u00f5es para o switch conectado a {zone}: estado {state}", - "title": "Configurar o switch de sa\u00edda" + "title": "Configure o interruptor de sa\u00edda" } } } diff --git a/homeassistant/components/kostal_plenticore/select.py b/homeassistant/components/kostal_plenticore/select.py index 1844f0894a5..7ac06f2ebef 100644 --- a/homeassistant/components/kostal_plenticore/select.py +++ b/homeassistant/components/kostal_plenticore/select.py @@ -24,6 +24,8 @@ async def async_setup_entry( ) -> None: """Add kostal plenticore Select widget.""" plenticore: Plenticore = hass.data[DOMAIN][entry.entry_id] + + available_settings_data = await plenticore.client.get_settings() select_data_update_coordinator = SelectDataUpdateCoordinator( hass, _LOGGER, @@ -32,23 +34,34 @@ async def async_setup_entry( plenticore, ) - async_add_entities( - PlenticoreDataSelect( - select_data_update_coordinator, - entry_id=entry.entry_id, - platform_name=entry.title, - device_class="kostal_plenticore__battery", - module_id=select.module_id, - data_id=select.data_id, - name=select.name, - current_option="None", - options=select.options, - is_on=select.is_on, - device_info=plenticore.device_info, - unique_id=f"{entry.entry_id}_{select.module_id}", + entities = [] + for select in SELECT_SETTINGS_DATA: + if select.module_id not in available_settings_data: + continue + needed_data_ids = {data_id for data_id in select.options if data_id != "None"} + available_data_ids = { + setting.id for setting in available_settings_data[select.module_id] + } + if not needed_data_ids <= available_data_ids: + continue + entities.append( + PlenticoreDataSelect( + select_data_update_coordinator, + entry_id=entry.entry_id, + platform_name=entry.title, + device_class="kostal_plenticore__battery", + module_id=select.module_id, + data_id=select.data_id, + name=select.name, + current_option="None", + options=select.options, + is_on=select.is_on, + device_info=plenticore.device_info, + unique_id=f"{entry.entry_id}_{select.module_id}", + ) ) - for select in SELECT_SETTINGS_DATA - ) + + async_add_entities(entities) class PlenticoreDataSelect(CoordinatorEntity, SelectEntity, ABC): diff --git a/homeassistant/components/kostal_plenticore/translations/ca.json b/homeassistant/components/kostal_plenticore/translations/ca.json index 2ce39d904a6..fe22d6e3acd 100644 --- a/homeassistant/components/kostal_plenticore/translations/ca.json +++ b/homeassistant/components/kostal_plenticore/translations/ca.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Inversor solar Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/de.json b/homeassistant/components/kostal_plenticore/translations/de.json index dfd568f937c..095487fff3f 100644 --- a/homeassistant/components/kostal_plenticore/translations/de.json +++ b/homeassistant/components/kostal_plenticore/translations/de.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore Solar-Wechselrichter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/el.json b/homeassistant/components/kostal_plenticore/translations/el.json index 518070a76ef..c29ca36535e 100644 --- a/homeassistant/components/kostal_plenticore/translations/el.json +++ b/homeassistant/components/kostal_plenticore/translations/el.json @@ -16,6 +16,5 @@ } } } - }, - "title": "\u0397\u03bb\u03b9\u03b1\u03ba\u03cc\u03c2 \u03bc\u03b5\u03c4\u03b1\u03c4\u03c1\u03bf\u03c0\u03ad\u03b1\u03c2 Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/en.json b/homeassistant/components/kostal_plenticore/translations/en.json index a058336b077..f9334da7aad 100644 --- a/homeassistant/components/kostal_plenticore/translations/en.json +++ b/homeassistant/components/kostal_plenticore/translations/en.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore Solar Inverter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/es.json b/homeassistant/components/kostal_plenticore/translations/es.json index e763acfbe4d..66eed9e0642 100644 --- a/homeassistant/components/kostal_plenticore/translations/es.json +++ b/homeassistant/components/kostal_plenticore/translations/es.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Inversor solar Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/et.json b/homeassistant/components/kostal_plenticore/translations/et.json index c96935d5db8..6b090a0c959 100644 --- a/homeassistant/components/kostal_plenticore/translations/et.json +++ b/homeassistant/components/kostal_plenticore/translations/et.json @@ -16,6 +16,5 @@ } } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/fr.json b/homeassistant/components/kostal_plenticore/translations/fr.json index 66888e8879b..c0f59229275 100644 --- a/homeassistant/components/kostal_plenticore/translations/fr.json +++ b/homeassistant/components/kostal_plenticore/translations/fr.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Onduleur solaire Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/hu.json b/homeassistant/components/kostal_plenticore/translations/hu.json index 3ffe413a82b..10e87e8deca 100644 --- a/homeassistant/components/kostal_plenticore/translations/hu.json +++ b/homeassistant/components/kostal_plenticore/translations/hu.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore szol\u00e1r inverter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/id.json b/homeassistant/components/kostal_plenticore/translations/id.json index c249355f8ca..90c843f5d4d 100644 --- a/homeassistant/components/kostal_plenticore/translations/id.json +++ b/homeassistant/components/kostal_plenticore/translations/id.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Solar Inverter Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/it.json b/homeassistant/components/kostal_plenticore/translations/it.json index 8e46b765fe0..b2b7b688bc4 100644 --- a/homeassistant/components/kostal_plenticore/translations/it.json +++ b/homeassistant/components/kostal_plenticore/translations/it.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Inverter solare Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/ja.json b/homeassistant/components/kostal_plenticore/translations/ja.json index 8a16c8c918e..0526d7f7729 100644 --- a/homeassistant/components/kostal_plenticore/translations/ja.json +++ b/homeassistant/components/kostal_plenticore/translations/ja.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore Solar Inverter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/nl.json b/homeassistant/components/kostal_plenticore/translations/nl.json index 83a77fb6e0d..05bdafd606b 100644 --- a/homeassistant/components/kostal_plenticore/translations/nl.json +++ b/homeassistant/components/kostal_plenticore/translations/nl.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore omvormer voor zonne-energie" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/no.json b/homeassistant/components/kostal_plenticore/translations/no.json index 0f0d77a83e6..b79a6b1795c 100644 --- a/homeassistant/components/kostal_plenticore/translations/no.json +++ b/homeassistant/components/kostal_plenticore/translations/no.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore Solar Inverter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/pl.json b/homeassistant/components/kostal_plenticore/translations/pl.json index 781bddfc979..c9025af7205 100644 --- a/homeassistant/components/kostal_plenticore/translations/pl.json +++ b/homeassistant/components/kostal_plenticore/translations/pl.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Inwerter solarny Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/pt-BR.json b/homeassistant/components/kostal_plenticore/translations/pt-BR.json index a670c5a41be..b829ba6e92b 100644 --- a/homeassistant/components/kostal_plenticore/translations/pt-BR.json +++ b/homeassistant/components/kostal_plenticore/translations/pt-BR.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Inversor Solar Kostal Plenticore" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/ru.json b/homeassistant/components/kostal_plenticore/translations/ru.json index d272fd0f304..02ecda0034c 100644 --- a/homeassistant/components/kostal_plenticore/translations/ru.json +++ b/homeassistant/components/kostal_plenticore/translations/ru.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore Solar Inverter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/tr.json b/homeassistant/components/kostal_plenticore/translations/tr.json index f0742d20e78..78ade55a949 100644 --- a/homeassistant/components/kostal_plenticore/translations/tr.json +++ b/homeassistant/components/kostal_plenticore/translations/tr.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore Solar \u0130nverter" + } } \ No newline at end of file diff --git a/homeassistant/components/kostal_plenticore/translations/zh-Hant.json b/homeassistant/components/kostal_plenticore/translations/zh-Hant.json index f115cf74c89..fa1b3d064be 100644 --- a/homeassistant/components/kostal_plenticore/translations/zh-Hant.json +++ b/homeassistant/components/kostal_plenticore/translations/zh-Hant.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Kostal Plenticore \u592a\u967d\u80fd\u63db\u6d41\u5668" + } } \ No newline at end of file diff --git a/homeassistant/components/kraken/translations/it.json b/homeassistant/components/kraken/translations/it.json index 4b646be7a8d..2fabbc9ad5c 100644 --- a/homeassistant/components/kraken/translations/it.json +++ b/homeassistant/components/kraken/translations/it.json @@ -4,14 +4,14 @@ "already_configured": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "step": { "user": { "data": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "description": "Vuoi iniziare la configurazione?" } diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index be7b66177bb..0bef2245a7a 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -9,7 +9,7 @@ import pykulersky from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGBW_COLOR, - COLOR_MODE_RGBW, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -67,8 +67,8 @@ class KulerskyLight(LightEntity): """Initialize a Kuler Sky light.""" self._light = light self._available = False - self._attr_supported_color_modes = {COLOR_MODE_RGBW} - self._attr_color_mode = COLOR_MODE_RGBW + self._attr_supported_color_modes = {ColorMode.RGBW} + self._attr_color_mode = ColorMode.RGBW async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index c3882033caf..31aedab2fe6 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -5,11 +5,8 @@ from typing import Any, cast import pypck -from homeassistant.components.climate import ( - DOMAIN as DOMAIN_CLIMATE, - ClimateEntity, - const, -) +from homeassistant.components.climate import DOMAIN as DOMAIN_CLIMATE, ClimateEntity +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_TEMPERATURE, @@ -69,6 +66,8 @@ async def async_setup_entry( class LcnClimate(LcnEntity, ClimateEntity): """Representation of a LCN climate device.""" + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__( self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType ) -> None: @@ -90,6 +89,10 @@ class LcnClimate(LcnEntity, ClimateEntity): self._target_temperature = None self._is_on = True + self._attr_hvac_modes = [HVACMode.HEAT] + if self.is_lockable: + self._attr_hvac_modes.append(HVACMode.OFF) + async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" await super().async_added_to_hass() @@ -104,11 +107,6 @@ class LcnClimate(LcnEntity, ClimateEntity): await self.device_connection.cancel_status_request_handler(self.variable) await self.device_connection.cancel_status_request_handler(self.setpoint) - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return const.SUPPORT_TARGET_TEMPERATURE - @property def temperature_unit(self) -> str: """Return the unit of measurement.""" @@ -128,25 +126,14 @@ class LcnClimate(LcnEntity, ClimateEntity): return self._target_temperature @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ if self._is_on: - return const.HVAC_MODE_HEAT - return const.HVAC_MODE_OFF - - @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ - modes = [const.HVAC_MODE_HEAT] - if self.is_lockable: - modes.append(const.HVAC_MODE_OFF) - return modes + return HVACMode.HEAT + return HVACMode.OFF @property def max_temp(self) -> float: @@ -158,16 +145,16 @@ class LcnClimate(LcnEntity, ClimateEntity): """Return the minimum temperature.""" return cast(float, self._min_temp) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == const.HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: if not await self.device_connection.lock_regulator( self.regulator_id, False ): return self._is_on = True self.async_write_ha_state() - elif hvac_mode == const.HVAC_MODE_OFF: + elif hvac_mode == HVACMode.OFF: if not await self.device_connection.lock_regulator(self.regulator_id, True): return self._is_on = False diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 260dd2212ea..38480cc3124 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -9,9 +9,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, DOMAIN as DOMAIN_LIGHT, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES @@ -64,6 +64,8 @@ async def async_setup_entry( class LcnOutputLight(LcnEntity, LightEntity): """Representation of a LCN light for output ports.""" + _attr_supported_features = LightEntityFeature.TRANSITION + def __init__( self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType ) -> None: @@ -81,6 +83,12 @@ class LcnOutputLight(LcnEntity, LightEntity): self._is_on = False self._is_dimming_to_zero = False + if self.dimmable: + self._attr_color_mode = ColorMode.BRIGHTNESS + else: + self._attr_color_mode = ColorMode.ONOFF + self._attr_supported_color_modes = {self._attr_color_mode} + async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" await super().async_added_to_hass() @@ -93,13 +101,6 @@ class LcnOutputLight(LcnEntity, LightEntity): if not self.device_connection.is_group: await self.device_connection.cancel_status_request_handler(self.output) - @property - def supported_features(self) -> int: - """Flag supported features.""" - if self.dimmable: - return SUPPORT_TRANSITION | SUPPORT_BRIGHTNESS - return SUPPORT_TRANSITION - @property def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" @@ -167,6 +168,9 @@ class LcnOutputLight(LcnEntity, LightEntity): class LcnRelayLight(LcnEntity, LightEntity): """Representation of a LCN light for relay ports.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__( self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType ) -> None: diff --git a/homeassistant/components/lg_netcast/media_player.py b/homeassistant/components/lg_netcast/media_player.py index a76d74481d9..a36ac83d37c 100644 --- a/homeassistant/components/lg_netcast/media_player.py +++ b/homeassistant/components/lg_netcast/media_player.py @@ -1,32 +1,19 @@ """Support for LG TV running on NetCast 3 or 4.""" from __future__ import annotations -from datetime import datetime, timedelta +from datetime import datetime from pylgnetcast import LgNetCastClient, LgNetCastError from requests import RequestException import voluptuous as vol -from homeassistant import util from homeassistant.components.media_player import ( PLATFORM_SCHEMA, MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_CHANNEL from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_HOST, @@ -47,20 +34,17 @@ DEFAULT_NAME = "LG TV Remote" CONF_ON_ACTION = "turn_on_action" -MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) - SUPPORT_LGTV = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -122,7 +106,6 @@ class LgTVDevice(MediaPlayerEntity): except (LgNetCastError, RequestException): self._state = STATE_OFF - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update(self): """Retrieve the latest data from the LG TV.""" @@ -226,7 +209,7 @@ class LgTVDevice(MediaPlayerEntity): def supported_features(self): """Flag media player features that are supported.""" if self._on_action_script: - return SUPPORT_LGTV | SUPPORT_TURN_ON + return SUPPORT_LGTV | MediaPlayerEntityFeature.TURN_ON return SUPPORT_LGTV @property diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index cee8e5114c1..569678c8c15 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -3,25 +3,15 @@ from __future__ import annotations import temescal -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -SUPPORT_LG = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_SELECT_SOURCE - | SUPPORT_SELECT_SOUND_MODE -) - def setup_platform( hass: HomeAssistant, @@ -37,6 +27,13 @@ def setup_platform( class LGDevice(MediaPlayerEntity): """Representation of an LG soundbar device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.SELECT_SOUND_MODE + ) + def __init__(self, discovery_info): """Initialize the LG speakers.""" self._host = discovery_info["host"] @@ -189,11 +186,6 @@ class LGDevice(MediaPlayerEntity): sources.append(temescal.functions[function]) return sorted(sources) - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_LG - def set_volume_level(self, volume): """Set volume level, range 0..1.""" volume = volume * self._volume_max diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index a6dd643655f..428ae8745e0 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from datetime import timedelta from functools import partial +from ipaddress import IPv4Address import logging import math @@ -13,6 +14,7 @@ from awesomeversion import AwesomeVersion import voluptuous as vol from homeassistant import util +from homeassistant.components import network from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, @@ -69,8 +71,8 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=10) DISCOVERY_INTERVAL = 60 -MESSAGE_TIMEOUT = 1.0 -MESSAGE_RETRIES = 8 +MESSAGE_TIMEOUT = 1 +MESSAGE_RETRIES = 3 UNAVAILABLE_GRACE = 90 FIX_MAC_FW = AwesomeVersion("3.70") @@ -193,20 +195,18 @@ async def async_setup_entry( """Set up LIFX from a config entry.""" # Priority 1: manual config if not (interfaces := hass.data[LIFX_DOMAIN].get(DOMAIN)): - # Priority 2: scanned interfaces - lifx_ip_addresses = await aiolifx().LifxScan(hass.loop).scan() - interfaces = [{CONF_SERVER: ip} for ip in lifx_ip_addresses] - if not interfaces: - # Priority 3: default interface - interfaces = [{}] + # Priority 2: Home Assistant enabled interfaces + ip_addresses = ( + source_ip + for source_ip in await network.async_get_enabled_source_ips(hass) + if isinstance(source_ip, IPv4Address) and not source_ip.is_loopback + ) + interfaces = [{CONF_SERVER: str(ip)} for ip in ip_addresses] platform = entity_platform.async_get_current_platform() lifx_manager = LIFXManager(hass, platform, config_entry, async_add_entities) hass.data[DATA_LIFX_MANAGER] = lifx_manager - # This is to clean up old litter. Can be removed in Home Assistant 2022.5. - await lifx_manager.remove_empty_devices() - for interface in interfaces: lifx_manager.start_discovery(interface) @@ -262,6 +262,7 @@ class LIFXManager: def __init__(self, hass, platform, config_entry, async_add_entities): """Initialize the light.""" self.entities = {} + self.discoveries_inflight = {} self.hass = hass self.platform = platform self.config_entry = config_entry @@ -381,18 +382,23 @@ class LIFXManager: @callback def register(self, bulb): - """Handle aiolifx detected bulb.""" - self.hass.async_create_task(self.register_new_bulb(bulb)) + """Allow a single in-flight discovery per bulb.""" + if bulb.mac_addr not in self.discoveries_inflight: + self.discoveries_inflight[bulb.mac_addr] = bulb.ip_addr + _LOGGER.debug("Discovered %s (%s)", bulb.ip_addr, bulb.mac_addr) + self.hass.async_create_task(self.register_bulb(bulb)) + else: + _LOGGER.warning("Duplicate LIFX discovery response ignored") - async def register_new_bulb(self, bulb): - """Handle newly detected bulb.""" + async def register_bulb(self, bulb): + """Handle LIFX bulb registration lifecycle.""" if bulb.mac_addr in self.entities: entity = self.entities[bulb.mac_addr] entity.registered = True - _LOGGER.debug("%s register AGAIN", entity.who) + _LOGGER.debug("Reconnected to %s", entity.who) await entity.update_hass() else: - _LOGGER.debug("%s register NEW", bulb.ip_addr) + _LOGGER.debug("Connecting to %s (%s)", bulb.ip_addr, bulb.mac_addr) # Read initial state ack = AwaitAioLIFX().wait @@ -401,8 +407,8 @@ class LIFXManager: # can be ignored. version_resp = await ack(bulb.get_version) if version_resp and bulb.product in SWITCH_PRODUCT_IDS: - _LOGGER.warning( - "(Switch) action=skip_discovery, reason=unsupported, serial=%s, ip_addr=%s, type='LIFX Switch'", + _LOGGER.debug( + "Not connecting to LIFX Switch %s (%s)", str(bulb.mac_addr).replace(":", ""), bulb.ip_addr, ) @@ -411,7 +417,7 @@ class LIFXManager: color_resp = await ack(bulb.get_color) if color_resp is None or version_resp is None: - _LOGGER.error("Failed to initialize %s", bulb.ip_addr) + _LOGGER.error("Failed to connect to %s", bulb.ip_addr) bulb.registered = False else: bulb.timeout = MESSAGE_TIMEOUT @@ -425,27 +431,29 @@ class LIFXManager: else: entity = LIFXWhite(bulb, self.effects_conductor) - _LOGGER.debug("%s register READY", entity.who) + _LOGGER.debug("Connected to %s", entity.who) self.entities[bulb.mac_addr] = entity + self.discoveries_inflight.pop(bulb.mac_addr, None) self.async_add_entities([entity], True) @callback def unregister(self, bulb): - """Handle aiolifx disappearing bulbs.""" + """Disconnect and unregister non-responsive bulbs.""" if bulb.mac_addr in self.entities: entity = self.entities[bulb.mac_addr] - _LOGGER.debug("%s unregister", entity.who) + _LOGGER.debug("Disconnected from %s", entity.who) entity.registered = False entity.async_write_ha_state() - async def entity_registry_updated(self, event): + @callback + def entity_registry_updated(self, event): """Handle entity registry updated.""" if event.data["action"] == "remove": - await self.remove_empty_devices() + self.remove_empty_devices() - async def remove_empty_devices(self): + def remove_empty_devices(self): """Remove devices with no entities.""" - entity_reg = await er.async_get_registry(self.hass) + entity_reg = er.async_get(self.hass) device_reg = dr.async_get(self.hass) device_list = dr.async_entries_for_config_entry( device_reg, self.config_entry.entry_id @@ -456,7 +464,9 @@ class LIFXManager: device_entry.id, include_disabled_entities=True, ): - device_reg.async_remove_device(device_entry.id) + device_reg.async_update_device( + device_entry.id, remove_config_entry_id=self.config_entry.entry_id + ) class AwaitAioLIFX: @@ -551,8 +561,8 @@ class LIFXLight(LightEntity): @property def who(self): - """Return a string identifying the bulb.""" - return f"{self.bulb.ip_addr} ({self.name})" + """Return a string identifying the bulb by name and mac.""" + return f"{self.name} ({self.bulb.mac_addr})" @property def min_mireds(self): diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json index 9251bcb1f50..a7f266b6f7d 100644 --- a/homeassistant/components/lifx/manifest.json +++ b/homeassistant/components/lifx/manifest.json @@ -4,10 +4,32 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/lifx", "requirements": ["aiolifx==0.7.1", "aiolifx_effects==0.2.2"], + "dependencies": ["network"], "homekit": { - "models": ["LIFX"] + "models": [ + "LIFX A19", + "LIFX Beam", + "LIFX BR30", + "LIFX Candle", + "LIFX Clean", + "LIFX Color", + "LIFX DLCOL", + "LIFX Dlight", + "LIFX DLWW", + "LIFX Downlight", + "LIFX Filament", + "LIFX GU10", + "LIFX Lightstrip", + "LIFX Mini", + "LIFX Nightvision", + "LIFX Pls", + "LIFX Plus", + "LIFX Tile", + "LIFX White", + "LIFX Z" + ] }, - "codeowners": [], + "codeowners": ["@Djelibeybi"], "iot_class": "local_polling", "loggers": ["aiolifx", "aiolifx_effects", "bitstring"] } diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 36588704c85..4f3240d7b31 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -5,12 +5,14 @@ from collections.abc import Iterable import csv import dataclasses from datetime import timedelta +from enum import IntEnum import logging import os from typing import cast, final import voluptuous as vol +from homeassistant.backports.enum import StrEnum from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( SERVICE_TOGGLE, @@ -40,7 +42,17 @@ DATA_PROFILES = "light_profiles" ENTITY_ID_FORMAT = DOMAIN + ".{}" -# Bitfield of features supported by the light entity + +class LightEntityFeature(IntEnum): + """Supported features of the light entity.""" + + EFFECT = 4 + FLASH = 8 + TRANSITION = 32 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the LightEntityFeature enum instead. SUPPORT_BRIGHTNESS = 1 # Deprecated, replaced by color modes SUPPORT_COLOR_TEMP = 2 # Deprecated, replaced by color modes SUPPORT_EFFECT = 4 @@ -53,72 +65,92 @@ SUPPORT_WHITE_VALUE = 128 # Deprecated, replaced by color modes ATTR_COLOR_MODE = "color_mode" # List of color modes supported by the light ATTR_SUPPORTED_COLOR_MODES = "supported_color_modes" -# Possible color modes -COLOR_MODE_UNKNOWN = "unknown" # Ambiguous color mode -COLOR_MODE_ONOFF = "onoff" # Must be the only supported mode -COLOR_MODE_BRIGHTNESS = "brightness" # Must be the only supported mode + + +class ColorMode(StrEnum): + """Possible light color modes.""" + + UNKNOWN = "unknown" # Ambiguous color mode + ONOFF = "onoff" # Must be the only supported mode + BRIGHTNESS = "brightness" # Must be the only supported mode + COLOR_TEMP = "color_temp" + HS = "hs" + XY = "xy" + RGB = "rgb" + RGBW = "rgbw" + RGBWW = "rgbww" + WHITE = "white" # Must *NOT* be the only supported mode + + +# These COLOR_MODE_* constants are deprecated as of Home Assistant 2022.5. +# Please use the LightEntityFeature enum instead. +COLOR_MODE_UNKNOWN = "unknown" +COLOR_MODE_ONOFF = "onoff" +COLOR_MODE_BRIGHTNESS = "brightness" COLOR_MODE_COLOR_TEMP = "color_temp" COLOR_MODE_HS = "hs" COLOR_MODE_XY = "xy" COLOR_MODE_RGB = "rgb" COLOR_MODE_RGBW = "rgbw" COLOR_MODE_RGBWW = "rgbww" -COLOR_MODE_WHITE = "white" # Must *NOT* be the only supported mode +COLOR_MODE_WHITE = "white" VALID_COLOR_MODES = { - COLOR_MODE_ONOFF, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_XY, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_WHITE, + ColorMode.ONOFF, + ColorMode.BRIGHTNESS, + ColorMode.COLOR_TEMP, + ColorMode.HS, + ColorMode.XY, + ColorMode.RGB, + ColorMode.RGBW, + ColorMode.RGBWW, + ColorMode.WHITE, } -COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF} +COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {ColorMode.ONOFF} COLOR_MODES_COLOR = { - COLOR_MODE_HS, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_XY, + ColorMode.HS, + ColorMode.RGB, + ColorMode.RGBW, + ColorMode.RGBWW, + ColorMode.XY, } -def valid_supported_color_modes(color_modes: Iterable[str]) -> set[str]: +def valid_supported_color_modes( + color_modes: Iterable[ColorMode | str], +) -> set[ColorMode | str]: """Validate the given color modes.""" color_modes = set(color_modes) if ( not color_modes - or COLOR_MODE_UNKNOWN in color_modes - or (COLOR_MODE_BRIGHTNESS in color_modes and len(color_modes) > 1) - or (COLOR_MODE_ONOFF in color_modes and len(color_modes) > 1) - or (COLOR_MODE_WHITE in color_modes and not color_supported(color_modes)) + or ColorMode.UNKNOWN in color_modes + or (ColorMode.BRIGHTNESS in color_modes and len(color_modes) > 1) + or (ColorMode.ONOFF in color_modes and len(color_modes) > 1) + or (ColorMode.WHITE in color_modes and not color_supported(color_modes)) ): raise vol.Error(f"Invalid supported_color_modes {sorted(color_modes)}") return color_modes -def brightness_supported(color_modes: Iterable[str] | None) -> bool: +def brightness_supported(color_modes: Iterable[ColorMode | str] | None) -> bool: """Test if brightness is supported.""" if not color_modes: return False return any(mode in COLOR_MODES_BRIGHTNESS for mode in color_modes) -def color_supported(color_modes: Iterable[str] | None) -> bool: +def color_supported(color_modes: Iterable[ColorMode | str] | None) -> bool: """Test if color is supported.""" if not color_modes: return False return any(mode in COLOR_MODES_COLOR for mode in color_modes) -def color_temp_supported(color_modes: Iterable[str] | None) -> bool: +def color_temp_supported(color_modes: Iterable[ColorMode | str] | None) -> bool: """Test if color temperature is supported.""" if not color_modes: return False - return COLOR_MODE_COLOR_TEMP in color_modes + return ColorMode.COLOR_TEMP in color_modes def get_supported_color_modes(hass: HomeAssistant, entity_id: str) -> set | None: @@ -274,9 +306,9 @@ def filter_turn_off_params(light, params): """Filter out params not used in turn off or not supported by the light.""" supported_features = light.supported_features - if not supported_features & SUPPORT_FLASH: + if not supported_features & LightEntityFeature.FLASH: params.pop(ATTR_FLASH, None) - if not supported_features & SUPPORT_TRANSITION: + if not supported_features & LightEntityFeature.TRANSITION: params.pop(ATTR_TRANSITION, None) return {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)} @@ -286,11 +318,11 @@ def filter_turn_on_params(light, params): """Filter out params not supported by the light.""" supported_features = light.supported_features - if not supported_features & SUPPORT_EFFECT: + if not supported_features & LightEntityFeature.EFFECT: params.pop(ATTR_EFFECT, None) - if not supported_features & SUPPORT_FLASH: + if not supported_features & LightEntityFeature.FLASH: params.pop(ATTR_FLASH, None) - if not supported_features & SUPPORT_TRANSITION: + if not supported_features & LightEntityFeature.TRANSITION: params.pop(ATTR_TRANSITION, None) if not supported_features & SUPPORT_WHITE_VALUE: params.pop(ATTR_WHITE_VALUE, None) @@ -300,19 +332,19 @@ def filter_turn_on_params(light, params): ) if not brightness_supported(supported_color_modes): params.pop(ATTR_BRIGHTNESS, None) - if COLOR_MODE_COLOR_TEMP not in supported_color_modes: + if ColorMode.COLOR_TEMP not in supported_color_modes: params.pop(ATTR_COLOR_TEMP, None) - if COLOR_MODE_HS not in supported_color_modes: + if ColorMode.HS not in supported_color_modes: params.pop(ATTR_HS_COLOR, None) - if COLOR_MODE_RGB not in supported_color_modes: + if ColorMode.RGB not in supported_color_modes: params.pop(ATTR_RGB_COLOR, None) - if COLOR_MODE_RGBW not in supported_color_modes: + if ColorMode.RGBW not in supported_color_modes: params.pop(ATTR_RGBW_COLOR, None) - if COLOR_MODE_RGBWW not in supported_color_modes: + if ColorMode.RGBWW not in supported_color_modes: params.pop(ATTR_RGBWW_COLOR, None) - if COLOR_MODE_WHITE not in supported_color_modes: + if ColorMode.WHITE not in supported_color_modes: params.pop(ATTR_WHITE, None) - if COLOR_MODE_XY not in supported_color_modes: + if ColorMode.XY not in supported_color_modes: params.pop(ATTR_XY_COLOR, None) return params @@ -376,7 +408,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: # for legacy lights if ATTR_RGBW_COLOR in params: if ( - COLOR_MODE_RGBW in legacy_supported_color_modes + ColorMode.RGBW in legacy_supported_color_modes and not supported_color_modes ): rgbw_color = params.pop(ATTR_RGBW_COLOR) @@ -387,15 +419,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: if ATTR_COLOR_TEMP in params: if ( supported_color_modes - and COLOR_MODE_COLOR_TEMP not in supported_color_modes - and COLOR_MODE_RGBWW in supported_color_modes + and ColorMode.COLOR_TEMP not in supported_color_modes + and ColorMode.RGBWW in supported_color_modes ): color_temp = params.pop(ATTR_COLOR_TEMP) brightness = params.get(ATTR_BRIGHTNESS, light.brightness) params[ATTR_RGBWW_COLOR] = color_util.color_temperature_to_rgbww( color_temp, brightness, light.min_mireds, light.max_mireds ) - elif COLOR_MODE_COLOR_TEMP not in legacy_supported_color_modes: + elif ColorMode.COLOR_TEMP not in legacy_supported_color_modes: color_temp = params.pop(ATTR_COLOR_TEMP) if color_supported(legacy_supported_color_modes): temp_k = color_util.color_temperature_mired_to_kelvin(color_temp) @@ -417,80 +449,80 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: *rgbww_color, light.min_mireds, light.max_mireds ) params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) - elif ATTR_HS_COLOR in params and COLOR_MODE_HS not in supported_color_modes: + elif ATTR_HS_COLOR in params and ColorMode.HS not in supported_color_modes: hs_color = params.pop(ATTR_HS_COLOR) - if COLOR_MODE_RGB in supported_color_modes: + if ColorMode.RGB in supported_color_modes: params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) - elif COLOR_MODE_RGBW in supported_color_modes: + elif ColorMode.RGBW in supported_color_modes: rgb_color = color_util.color_hs_to_RGB(*hs_color) params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color) - elif COLOR_MODE_RGBWW in supported_color_modes: + elif ColorMode.RGBWW in supported_color_modes: rgb_color = color_util.color_hs_to_RGB(*hs_color) params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( *rgb_color, light.min_mireds, light.max_mireds ) - elif COLOR_MODE_XY in supported_color_modes: + elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) - elif ATTR_RGB_COLOR in params and COLOR_MODE_RGB not in supported_color_modes: + elif ATTR_RGB_COLOR in params and ColorMode.RGB not in supported_color_modes: rgb_color = params.pop(ATTR_RGB_COLOR) - if COLOR_MODE_RGBW in supported_color_modes: + if ColorMode.RGBW in supported_color_modes: params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color) - elif COLOR_MODE_RGBWW in supported_color_modes: + elif ColorMode.RGBWW in supported_color_modes: params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( *rgb_color, light.min_mireds, light.max_mireds ) - elif COLOR_MODE_HS in supported_color_modes: + elif ColorMode.HS in supported_color_modes: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) - elif COLOR_MODE_XY in supported_color_modes: + elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) - elif ATTR_XY_COLOR in params and COLOR_MODE_XY not in supported_color_modes: + elif ATTR_XY_COLOR in params and ColorMode.XY not in supported_color_modes: xy_color = params.pop(ATTR_XY_COLOR) - if COLOR_MODE_HS in supported_color_modes: + if ColorMode.HS in supported_color_modes: params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) - elif COLOR_MODE_RGB in supported_color_modes: + elif ColorMode.RGB in supported_color_modes: params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color) - elif COLOR_MODE_RGBW in supported_color_modes: + elif ColorMode.RGBW in supported_color_modes: rgb_color = color_util.color_xy_to_RGB(*xy_color) params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color) - elif COLOR_MODE_RGBWW in supported_color_modes: + elif ColorMode.RGBWW in supported_color_modes: rgb_color = color_util.color_xy_to_RGB(*xy_color) params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( *rgb_color, light.min_mireds, light.max_mireds ) - elif ATTR_RGBW_COLOR in params and COLOR_MODE_RGBW not in supported_color_modes: + elif ATTR_RGBW_COLOR in params and ColorMode.RGBW not in supported_color_modes: rgbw_color = params.pop(ATTR_RGBW_COLOR) rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color) - if COLOR_MODE_RGB in supported_color_modes: + if ColorMode.RGB in supported_color_modes: params[ATTR_RGB_COLOR] = rgb_color - elif COLOR_MODE_RGBWW in supported_color_modes: + elif ColorMode.RGBWW in supported_color_modes: params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( *rgb_color, light.min_mireds, light.max_mireds ) - elif COLOR_MODE_HS in supported_color_modes: + elif ColorMode.HS in supported_color_modes: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) - elif COLOR_MODE_XY in supported_color_modes: + elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) elif ( - ATTR_RGBWW_COLOR in params and COLOR_MODE_RGBWW not in supported_color_modes + ATTR_RGBWW_COLOR in params and ColorMode.RGBWW not in supported_color_modes ): rgbww_color = params.pop(ATTR_RGBWW_COLOR) rgb_color = color_util.color_rgbww_to_rgb( *rgbww_color, light.min_mireds, light.max_mireds ) - if COLOR_MODE_RGB in supported_color_modes: + if ColorMode.RGB in supported_color_modes: params[ATTR_RGB_COLOR] = rgb_color - elif COLOR_MODE_RGBW in supported_color_modes: + elif ColorMode.RGBW in supported_color_modes: params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color) - elif COLOR_MODE_HS in supported_color_modes: + elif ColorMode.HS in supported_color_modes: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) - elif COLOR_MODE_XY in supported_color_modes: + elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) # If both white and brightness are specified, override white if ( supported_color_modes and ATTR_WHITE in params - and COLOR_MODE_WHITE in supported_color_modes + and ColorMode.WHITE in supported_color_modes ): params[ATTR_WHITE] = params.pop(ATTR_BRIGHTNESS, params[ATTR_WHITE]) @@ -674,8 +706,21 @@ class Profiles: if (profile := self.data.get(name)) is None: return - if profile.hs_color is not None: - params.setdefault(ATTR_HS_COLOR, profile.hs_color) + color_attributes = ( + ATTR_COLOR_NAME, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, + ATTR_XY_COLOR, + ATTR_WHITE, + ) + + if profile.hs_color is not None and not any( + color_attribute in params for color_attribute in color_attributes + ): + params[ATTR_HS_COLOR] = profile.hs_color if profile.brightness is not None: params.setdefault(ATTR_BRIGHTNESS, profile.brightness) if profile.transition is not None: @@ -692,7 +737,7 @@ class LightEntity(ToggleEntity): entity_description: LightEntityDescription _attr_brightness: int | None = None - _attr_color_mode: str | None = None + _attr_color_mode: ColorMode | str | None = None _attr_color_temp: int | None = None _attr_effect_list: list[str] | None = None _attr_effect: str | None = None @@ -702,7 +747,7 @@ class LightEntity(ToggleEntity): _attr_rgb_color: tuple[int, int, int] | None = None _attr_rgbw_color: tuple[int, int, int, int] | None = None _attr_rgbww_color: tuple[int, int, int, int, int] | None = None - _attr_supported_color_modes: set[str] | None = None + _attr_supported_color_modes: set[ColorMode] | set[str] | None = None _attr_supported_features: int = 0 _attr_xy_color: tuple[float, float] | None = None @@ -712,7 +757,7 @@ class LightEntity(ToggleEntity): return self._attr_brightness @property - def color_mode(self) -> str | None: + def color_mode(self) -> ColorMode | str | None: """Return the color mode of the light.""" return self._attr_color_mode @@ -725,20 +770,20 @@ class LightEntity(ToggleEntity): supported = self._light_internal_supported_color_modes if ( - COLOR_MODE_RGBW in supported + ColorMode.RGBW in supported and self.white_value is not None and self.hs_color is not None ): - return COLOR_MODE_RGBW - if COLOR_MODE_HS in supported and self.hs_color is not None: - return COLOR_MODE_HS - if COLOR_MODE_COLOR_TEMP in supported and self.color_temp is not None: - return COLOR_MODE_COLOR_TEMP - if COLOR_MODE_BRIGHTNESS in supported and self.brightness is not None: - return COLOR_MODE_BRIGHTNESS - if COLOR_MODE_ONOFF in supported: - return COLOR_MODE_ONOFF - return COLOR_MODE_UNKNOWN + return ColorMode.RGBW + if ColorMode.HS in supported and self.hs_color is not None: + return ColorMode.HS + if ColorMode.COLOR_TEMP in supported and self.color_temp is not None: + return ColorMode.COLOR_TEMP + if ColorMode.BRIGHTNESS in supported and self.brightness is not None: + return ColorMode.BRIGHTNESS + if ColorMode.ONOFF in supported: + return ColorMode.ONOFF + return ColorMode.UNKNOWN return color_mode @@ -827,42 +872,42 @@ class LightEntity(ToggleEntity): supported_features = self.supported_features supported_color_modes = self._light_internal_supported_color_modes - if COLOR_MODE_COLOR_TEMP in supported_color_modes: + if ColorMode.COLOR_TEMP in supported_color_modes: data[ATTR_MIN_MIREDS] = self.min_mireds data[ATTR_MAX_MIREDS] = self.max_mireds - if supported_features & SUPPORT_EFFECT: + if supported_features & LightEntityFeature.EFFECT: data[ATTR_EFFECT_LIST] = self.effect_list data[ATTR_SUPPORTED_COLOR_MODES] = sorted(supported_color_modes) return data - def _light_internal_convert_color(self, color_mode: str) -> dict: + def _light_internal_convert_color(self, color_mode: ColorMode | str) -> dict: data: dict[str, tuple] = {} - if color_mode == COLOR_MODE_HS and self.hs_color: + if color_mode == ColorMode.HS and self.hs_color: hs_color = self.hs_color data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) - elif color_mode == COLOR_MODE_XY and self.xy_color: + elif color_mode == ColorMode.XY and self.xy_color: xy_color = self.xy_color data[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) data[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color) data[ATTR_XY_COLOR] = (round(xy_color[0], 6), round(xy_color[1], 6)) - elif color_mode == COLOR_MODE_RGB and self.rgb_color: + elif color_mode == ColorMode.RGB and self.rgb_color: rgb_color = self.rgb_color data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3]) data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) - elif color_mode == COLOR_MODE_RGBW and self._light_internal_rgbw_color: + elif color_mode == ColorMode.RGBW and self._light_internal_rgbw_color: rgbw_color = self._light_internal_rgbw_color rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color) data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3]) data[ATTR_RGBW_COLOR] = tuple(int(x) for x in rgbw_color[0:4]) data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) - elif color_mode == COLOR_MODE_RGBWW and self.rgbww_color: + elif color_mode == ColorMode.RGBWW and self.rgbww_color: rgbww_color = self.rgbww_color rgb_color = color_util.color_rgbww_to_rgb( *rgbww_color, self.min_mireds, self.max_mireds @@ -871,7 +916,7 @@ class LightEntity(ToggleEntity): data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3]) data[ATTR_RGBWW_COLOR] = tuple(int(x) for x in rgbww_color[0:5]) data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) - elif color_mode == COLOR_MODE_COLOR_TEMP and self.color_temp: + elif color_mode == ColorMode.COLOR_TEMP and self.color_temp: hs_color = color_util.color_temperature_to_hs( color_util.color_temperature_mired_to_kelvin(self.color_temp) ) @@ -909,10 +954,10 @@ class LightEntity(ToggleEntity): # Add warning in 2021.6, remove in 2021.10 data[ATTR_BRIGHTNESS] = self.brightness - if color_mode == COLOR_MODE_COLOR_TEMP: + if color_mode == ColorMode.COLOR_TEMP: data[ATTR_COLOR_TEMP] = self.color_temp - if color_mode in COLOR_MODES_COLOR or color_mode == COLOR_MODE_COLOR_TEMP: + if color_mode in COLOR_MODES_COLOR or color_mode == ColorMode.COLOR_TEMP: data.update(self._light_internal_convert_color(color_mode)) if supported_features & SUPPORT_COLOR_TEMP and not self.supported_color_modes: @@ -925,40 +970,40 @@ class LightEntity(ToggleEntity): # Add warning in 2021.6, remove in 2021.10 data[ATTR_WHITE_VALUE] = self.white_value if self.hs_color is not None: - data.update(self._light_internal_convert_color(COLOR_MODE_HS)) + data.update(self._light_internal_convert_color(ColorMode.HS)) - if supported_features & SUPPORT_EFFECT: + if supported_features & LightEntityFeature.EFFECT: data[ATTR_EFFECT] = self.effect return {key: val for key, val in data.items() if val is not None} @property - def _light_internal_supported_color_modes(self) -> set: + def _light_internal_supported_color_modes(self) -> set[ColorMode] | set[str]: """Calculate supported color modes with backwards compatibility.""" - supported_color_modes = self.supported_color_modes + if self.supported_color_modes is not None: + return self.supported_color_modes - if supported_color_modes is None: - # Backwards compatibility for supported_color_modes added in 2021.4 - # Add warning in 2021.6, remove in 2021.10 - supported_features = self.supported_features - supported_color_modes = set() + # Backwards compatibility for supported_color_modes added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + supported_features = self.supported_features + supported_color_modes: set[ColorMode] = set() - if supported_features & SUPPORT_COLOR_TEMP: - supported_color_modes.add(COLOR_MODE_COLOR_TEMP) - if supported_features & SUPPORT_COLOR: - supported_color_modes.add(COLOR_MODE_HS) - if supported_features & SUPPORT_WHITE_VALUE: - supported_color_modes.add(COLOR_MODE_RGBW) - if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes: - supported_color_modes = {COLOR_MODE_BRIGHTNESS} + if supported_features & SUPPORT_COLOR_TEMP: + supported_color_modes.add(ColorMode.COLOR_TEMP) + if supported_features & SUPPORT_COLOR: + supported_color_modes.add(ColorMode.HS) + if supported_features & SUPPORT_WHITE_VALUE: + supported_color_modes.add(ColorMode.RGBW) + if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes: + supported_color_modes = {ColorMode.BRIGHTNESS} - if not supported_color_modes: - supported_color_modes = {COLOR_MODE_ONOFF} + if not supported_color_modes: + supported_color_modes = {ColorMode.ONOFF} return supported_color_modes @property - def supported_color_modes(self) -> set[str] | None: + def supported_color_modes(self) -> set[ColorMode] | set[str] | None: """Flag supported color modes.""" return self._attr_supported_color_modes @@ -979,7 +1024,7 @@ def legacy_supported_features( supported_features |= SUPPORT_COLOR if any(mode in supported_color_modes for mode in COLOR_MODES_BRIGHTNESS): supported_features |= SUPPORT_BRIGHTNESS - if COLOR_MODE_COLOR_TEMP in supported_color_modes: + if ColorMode.COLOR_TEMP in supported_color_modes: supported_features |= SUPPORT_COLOR_TEMP return supported_features diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index 16140b3a2b1..285e34ebe08 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -24,9 +24,9 @@ from . import ( ATTR_FLASH, DOMAIN, FLASH_SHORT, - SUPPORT_FLASH, VALID_BRIGHTNESS_PCT, VALID_FLASH, + LightEntityFeature, brightness_supported, get_supported_color_modes, ) @@ -116,7 +116,7 @@ async def async_get_actions( ) ) - if supported_features & SUPPORT_FLASH: + if supported_features & LightEntityFeature.FLASH: actions.append({**base_action, CONF_TYPE: TYPE_FLASH}) return actions @@ -144,7 +144,7 @@ async def async_get_action_capabilities( if brightness_supported(supported_color_modes): extra_fields[vol.Optional(ATTR_BRIGHTNESS_PCT)] = VALID_BRIGHTNESS_PCT - if supported_features & SUPPORT_FLASH: + if supported_features & LightEntityFeature.FLASH: extra_fields[vol.Optional(ATTR_FLASH)] = VALID_FLASH return {"extra_fields": vol.Schema(extra_fields)} if extra_fields else {} diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index d60e0a10f3a..5e60c616500 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -33,15 +33,8 @@ from . import ( ATTR_WHITE, ATTR_WHITE_VALUE, ATTR_XY_COLOR, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_UNKNOWN, - COLOR_MODE_WHITE, - COLOR_MODE_XY, DOMAIN, + ColorMode, ) _LOGGER = logging.getLogger(__name__) @@ -79,13 +72,13 @@ class ColorModeAttr(NamedTuple): COLOR_MODE_TO_ATTRIBUTE = { - COLOR_MODE_COLOR_TEMP: ColorModeAttr(ATTR_COLOR_TEMP, ATTR_COLOR_TEMP), - COLOR_MODE_HS: ColorModeAttr(ATTR_HS_COLOR, ATTR_HS_COLOR), - COLOR_MODE_RGB: ColorModeAttr(ATTR_RGB_COLOR, ATTR_RGB_COLOR), - COLOR_MODE_RGBW: ColorModeAttr(ATTR_RGBW_COLOR, ATTR_RGBW_COLOR), - COLOR_MODE_RGBWW: ColorModeAttr(ATTR_RGBWW_COLOR, ATTR_RGBWW_COLOR), - COLOR_MODE_WHITE: ColorModeAttr(ATTR_WHITE, ATTR_BRIGHTNESS), - COLOR_MODE_XY: ColorModeAttr(ATTR_XY_COLOR, ATTR_XY_COLOR), + ColorMode.COLOR_TEMP: ColorModeAttr(ATTR_COLOR_TEMP, ATTR_COLOR_TEMP), + ColorMode.HS: ColorModeAttr(ATTR_HS_COLOR, ATTR_HS_COLOR), + ColorMode.RGB: ColorModeAttr(ATTR_RGB_COLOR, ATTR_RGB_COLOR), + ColorMode.RGBW: ColorModeAttr(ATTR_RGBW_COLOR, ATTR_RGBW_COLOR), + ColorMode.RGBWW: ColorModeAttr(ATTR_RGBWW_COLOR, ATTR_RGBWW_COLOR), + ColorMode.WHITE: ColorModeAttr(ATTR_WHITE, ATTR_BRIGHTNESS), + ColorMode.XY: ColorModeAttr(ATTR_XY_COLOR, ATTR_XY_COLOR), } DEPRECATED_GROUP = [ @@ -105,11 +98,11 @@ DEPRECATION_WARNING = ( def _color_mode_same(cur_state: State, state: State) -> bool: """Test if color_mode is same.""" - cur_color_mode = cur_state.attributes.get(ATTR_COLOR_MODE, COLOR_MODE_UNKNOWN) - saved_color_mode = state.attributes.get(ATTR_COLOR_MODE, COLOR_MODE_UNKNOWN) + cur_color_mode = cur_state.attributes.get(ATTR_COLOR_MODE, ColorMode.UNKNOWN) + saved_color_mode = state.attributes.get(ATTR_COLOR_MODE, ColorMode.UNKNOWN) # Guard for scenes etc. which where created before color modes were introduced - if saved_color_mode == COLOR_MODE_UNKNOWN: + if saved_color_mode == ColorMode.UNKNOWN: return True return cast(bool, cur_color_mode == saved_color_mode) @@ -161,8 +154,8 @@ async def _async_reproduce_state( service_data[attr] = state.attributes[attr] if ( - state.attributes.get(ATTR_COLOR_MODE, COLOR_MODE_UNKNOWN) - != COLOR_MODE_UNKNOWN + state.attributes.get(ATTR_COLOR_MODE, ColorMode.UNKNOWN) + != ColorMode.UNKNOWN ): # Remove deprecated white value if we got a valid color mode service_data.pop(ATTR_WHITE_VALUE, None) diff --git a/homeassistant/components/light/translations/hu.json b/homeassistant/components/light/translations/hu.json index 0be84e20763..986fd5d1787 100644 --- a/homeassistant/components/light/translations/hu.json +++ b/homeassistant/components/light/translations/hu.json @@ -13,6 +13,7 @@ "is_on": "{entity_name} fel van kapcsolva" }, "trigger_type": { + "changed_states": "{entity_name} be- vagy kikapcsolt", "toggled": "{entity_name} \u00e1tkapcsolt", "turned_off": "{entity_name} le lett kapcsolva", "turned_on": "{entity_name} fel lett kapcsolva" diff --git a/homeassistant/components/lightwave/climate.py b/homeassistant/components/lightwave/climate.py index d2b0c789441..8076cbc058b 100644 --- a/homeassistant/components/lightwave/climate.py +++ b/homeassistant/components/lightwave/climate.py @@ -4,12 +4,13 @@ from __future__ import annotations from homeassistant.components.climate import ( DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, ClimateEntity, ) -from homeassistant.components.climate.const import CURRENT_HVAC_HEAT, CURRENT_HVAC_OFF +from homeassistant.components.climate.const import ( + ClimateEntityFeature, + HVACAction, + HVACMode, +) from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -42,11 +43,11 @@ async def async_setup_platform( class LightwaveTrv(ClimateEntity): """Representation of a LightWaveRF TRV.""" - _attr_hvac_mode = HVAC_MODE_HEAT - _attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF] + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] _attr_min_temp = DEFAULT_MIN_TEMP _attr_max_temp = DEFAULT_MAX_TEMP - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_target_temperature_step = 0.5 _attr_temperature_unit = TEMP_CELSIUS @@ -79,9 +80,9 @@ class LightwaveTrv(ClimateEntity): self._inhibit = 0 if trv_output is not None: if trv_output > 0: - self._attr_hvac_action = CURRENT_HVAC_HEAT + self._attr_hvac_action = HVACAction.HEATING else: - self._attr_hvac_action = CURRENT_HVAC_OFF + self._attr_hvac_action = HVACAction.OFF @property def target_temperature(self): @@ -103,5 +104,5 @@ class LightwaveTrv(ClimateEntity): self._device_id, self._attr_target_temperature, self._attr_name ) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set HVAC Mode for TRV.""" diff --git a/homeassistant/components/lightwave/light.py b/homeassistant/components/lightwave/light.py index 4a261c39361..8d46592f439 100644 --- a/homeassistant/components/lightwave/light.py +++ b/homeassistant/components/lightwave/light.py @@ -1,11 +1,7 @@ """Support for LightwaveRF lights.""" from __future__ import annotations -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -39,7 +35,8 @@ async def async_setup_platform( class LWRFLight(LightEntity): """Representation of a LightWaveRF light.""" - _attr_supported_features = SUPPORT_BRIGHTNESS + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} _attr_should_poll = False def __init__(self, name, device_id, lwlink): diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index 49964b93b88..aae28536849 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -4,9 +4,9 @@ import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -41,6 +41,10 @@ async def async_setup_entry( class LiteJetLight(LightEntity): """Representation of a single LiteJet light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = LightEntityFeature.TRANSITION + def __init__(self, config_entry, lj, i, name): # pylint: disable=invalid-name """Initialize a LiteJet light.""" self._config_entry = config_entry @@ -63,11 +67,6 @@ class LiteJetLight(LightEntity): _LOGGER.debug("Updating due to notification for %s", self._name) self.schedule_update_ha_state(True) - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION - @property def name(self): """Return the light's name.""" diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 5af6c5b5ef3..fe20c6bfe50 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -13,12 +13,8 @@ from homeassistant.components.vacuum import ( STATE_DOCKED, STATE_ERROR, STATE_PAUSED, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STATUS, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF @@ -32,9 +28,6 @@ from .hub import LitterRobotHub _LOGGER = logging.getLogger(__name__) -SUPPORT_LITTERROBOT = ( - SUPPORT_START | SUPPORT_STATE | SUPPORT_STATUS | SUPPORT_TURN_OFF | SUPPORT_TURN_ON -) TYPE_LITTER_BOX = "Litter Box" SERVICE_RESET_WASTE_DRAWER = "reset_waste_drawer" @@ -81,10 +74,13 @@ async def async_setup_entry( class LitterRobotCleaner(LitterRobotControlEntity, StateVacuumEntity): """Litter-Robot "Vacuum" Cleaner.""" - @property - def supported_features(self) -> int: - """Flag cleaner robot features that are supported.""" - return SUPPORT_LITTERROBOT + _attr_supported_features = ( + VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.TURN_OFF + | VacuumEntityFeature.TURN_ON + ) @property def state(self) -> str: diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 60c7c91152f..b94cd33a015 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta +from enum import IntEnum import functools as ft import logging from typing import Any, final @@ -46,7 +47,15 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) LOCK_SERVICE_SCHEMA = make_entity_service_schema({vol.Optional(ATTR_CODE): cv.string}) -# Bitfield of features supported by the lock entity + +class LockEntityFeature(IntEnum): + """Supported features of the lock entity.""" + + OPEN = 1 + + +# The SUPPORT_OPEN constant is deprecated as of Home Assistant 2022.5. +# Please use the LockEntityFeature enum instead. SUPPORT_OPEN = 1 PROP_TO_ATTR = {"changed_by": ATTR_CHANGED_BY, "code_format": ATTR_CODE_FORMAT} diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index 50c205d113a..092aff8878d 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -18,7 +18,7 @@ from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import get_supported_features -from . import DOMAIN, SUPPORT_OPEN +from . import DOMAIN, LockEntityFeature ACTION_TYPES = {"lock", "unlock", "open"} @@ -54,7 +54,7 @@ async def async_get_actions( actions.append({**base_action, CONF_TYPE: "lock"}) actions.append({**base_action, CONF_TYPE: "unlock"}) - if supported_features & (SUPPORT_OPEN): + if supported_features & (LockEntityFeature.OPEN): actions.append({**base_action, CONF_TYPE: "open"}) return actions diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py index 896d58753fd..097b64ac208 100644 --- a/homeassistant/components/logi_circle/camera.py +++ b/homeassistant/components/logi_circle/camera.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import timedelta import logging -from homeassistant.components.camera import SUPPORT_ON_OFF, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import get_ffmpeg_manager from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -62,6 +62,8 @@ async def async_setup_entry( class LogiCam(Camera): """An implementation of a Logi Circle camera.""" + _attr_supported_features = CameraEntityFeature.ON_OFF + def __init__(self, camera, device_info, ffmpeg): """Initialize Logi Circle camera.""" super().__init__() @@ -123,11 +125,6 @@ class LogiCam(Camera): """Return the name of this camera.""" return self._name - @property - def supported_features(self): - """Logi Circle camera's support turning on and off ("soft" switch).""" - return SUPPORT_ON_OFF - @property def device_info(self) -> DeviceInfo: """Return information about the device.""" diff --git a/homeassistant/components/logi_circle/translations/hu.json b/homeassistant/components/logi_circle/translations/hu.json index f79ab3944dc..947f11e4907 100644 --- a/homeassistant/components/logi_circle/translations/hu.json +++ b/homeassistant/components/logi_circle/translations/hu.json @@ -8,12 +8,12 @@ }, "error": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", - "follow_link": "K\u00e9rem, k\u00f6vesse a hivatkoz\u00e1st \u00e9s hiteles\u00edtse mag\u00e1t miel\u0151tt megnyomn\u00e1 a K\u00fcld\u00e9s gombot", + "follow_link": "K\u00e9rem, k\u00f6vesse a hivatkoz\u00e1st \u00e9s hiteles\u00edtse mag\u00e1t miel\u0151tt folytatn\u00e1", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "auth": { - "description": "K\u00e9rj\u00fck, k\u00f6vesse az al\u00e1bbi linket, \u00e9s ** Fogadja el ** a LogiCircle -fi\u00f3kj\u00e1hoz val\u00f3 hozz\u00e1f\u00e9r\u00e9st, majd t\u00e9rjen vissza, \u00e9s nyomja meg az al\u00e1bbi ** K\u00fcld\u00e9s ** gombot. \n\n [Link]({authorization_url})", + "description": "K\u00e9rj\u00fck, k\u00f6vesse az al\u00e1bbi linket, \u00e9s ** Fogadja el ** a LogiCircle -fi\u00f3kj\u00e1hoz val\u00f3 hozz\u00e1f\u00e9r\u00e9st, majd t\u00e9rjen vissza, \u00e9s nyomja meg az al\u00e1bbi **Mehet** gombot. \n\n [Link]({authorization_url})", "title": "Hiteles\u00edt\u00e9s a LogiCircle seg\u00edts\u00e9g\u00e9vel" }, "user": { diff --git a/homeassistant/components/logi_circle/translations/it.json b/homeassistant/components/logi_circle/translations/it.json index 1299c4c53e4..fe17a15cae3 100644 --- a/homeassistant/components/logi_circle/translations/it.json +++ b/homeassistant/components/logi_circle/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "L'account \u00e8 gi\u00e0 configurato", "external_error": "Si \u00e8 verificata un'eccezione da un altro flusso.", "external_setup": "Logi Circle configurato con successo da un altro flusso.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione." }, "error": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", diff --git a/homeassistant/components/lookin/__init__.py b/homeassistant/components/lookin/__init__.py index c15d46c5158..555b8b551be 100644 --- a/homeassistant/components/lookin/__init__.py +++ b/homeassistant/components/lookin/__init__.py @@ -13,6 +13,7 @@ from aiolookin import ( LookInHttpProtocol, LookinUDPSubscriptions, MeteoSensor, + NoUsableService, Remote, start_lookin_udp, ) @@ -94,7 +95,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: lookin_device = await lookin_protocol.get_info() devices = await lookin_protocol.get_devices() - except (asyncio.TimeoutError, aiohttp.ClientError) as ex: + except (asyncio.TimeoutError, aiohttp.ClientError, NoUsableService) as ex: raise ConfigEntryNotReady from ex push_coordinator = LookinPushCoordinator(entry.title) diff --git a/homeassistant/components/lookin/climate.py b/homeassistant/components/lookin/climate.py index 79cac79cb17..b8042cef72a 100644 --- a/homeassistant/components/lookin/climate.py +++ b/homeassistant/components/lookin/climate.py @@ -14,17 +14,10 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MIDDLE, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, SWING_BOTH, SWING_OFF, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -41,17 +34,15 @@ from .coordinator import LookinDataUpdateCoordinator from .entity import LookinCoordinatorEntity from .models import LookinData -SUPPORT_FLAGS: int = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_SWING_MODE - LOOKIN_FAN_MODE_IDX_TO_HASS: Final = [FAN_AUTO, FAN_LOW, FAN_MIDDLE, FAN_HIGH] LOOKIN_SWING_MODE_IDX_TO_HASS: Final = [SWING_OFF, SWING_BOTH] LOOKIN_HVAC_MODE_IDX_TO_HASS: Final = [ - HVAC_MODE_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.DRY, + HVACMode.FAN_ONLY, ] HASS_TO_LOOKIN_HVAC_MODE: dict[str, int] = { @@ -102,10 +93,14 @@ class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity): _attr_current_humidity: float | None = None # type: ignore[assignment] _attr_temperature_unit = TEMP_CELSIUS - _attr_supported_features: int = SUPPORT_FLAGS + _attr_supported_features: int = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.SWING_MODE + ) _attr_fan_modes: list[str] = LOOKIN_FAN_MODE_IDX_TO_HASS _attr_swing_modes: list[str] = LOOKIN_SWING_MODE_IDX_TO_HASS - _attr_hvac_modes: list[str] = LOOKIN_HVAC_MODE_IDX_TO_HASS + _attr_hvac_modes: list[HVACMode] = LOOKIN_HVAC_MODE_IDX_TO_HASS _attr_min_temp = MIN_TEMP _attr_max_temp = MAX_TEMP _attr_target_temperature_step = PRECISION_WHOLE @@ -125,7 +120,7 @@ class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity): def _climate(self) -> Climate: return cast(Climate, self.coordinator.data) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the hvac mode of the device.""" if (mode := HASS_TO_LOOKIN_HVAC_MODE.get(hvac_mode)) is None: return @@ -140,7 +135,7 @@ class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity): lookin_index = LOOKIN_HVAC_MODE_IDX_TO_HASS if hvac_mode := kwargs.get(ATTR_HVAC_MODE): self._climate.hvac_mode = HASS_TO_LOOKIN_HVAC_MODE[hvac_mode] - elif self._climate.hvac_mode == lookin_index.index(HVAC_MODE_OFF): + elif self._climate.hvac_mode == lookin_index.index(HVACMode.OFF): # # If the device is off, and the user didn't specify an HVAC mode # (which is the default when using the HA UI), the device won't turn @@ -153,11 +148,11 @@ class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity): # meteo_data: MeteoSensor = self._meteo_coordinator.data if not (current_temp := meteo_data.temperature): - self._climate.hvac_mode = lookin_index.index(HVAC_MODE_AUTO) + self._climate.hvac_mode = lookin_index.index(HVACMode.AUTO) elif current_temp >= self._climate.temp_celsius: - self._climate.hvac_mode = lookin_index.index(HVAC_MODE_COOL) + self._climate.hvac_mode = lookin_index.index(HVACMode.COOL) else: - self._climate.hvac_mode = lookin_index.index(HVAC_MODE_HEAT) + self._climate.hvac_mode = lookin_index.index(HVACMode.HEAT) await self._async_update_conditioner() async def async_set_fan_mode(self, fan_mode: str) -> None: diff --git a/homeassistant/components/lookin/light.py b/homeassistant/components/lookin/light.py index 838201ea24f..d7ef4e62f2a 100644 --- a/homeassistant/components/lookin/light.py +++ b/homeassistant/components/lookin/light.py @@ -6,7 +6,7 @@ from typing import Any from aiolookin import Remote -from homeassistant.components.light import COLOR_MODE_ONOFF, LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -49,8 +49,8 @@ async def async_setup_entry( class LookinLightEntity(LookinPowerPushRemoteEntity, LightEntity): """A lookin IR controlled light.""" - _attr_supported_color_modes = {COLOR_MODE_ONOFF} - _attr_color_mode = COLOR_MODE_ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + _attr_color_mode = ColorMode.ONOFF async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" diff --git a/homeassistant/components/lookin/media_player.py b/homeassistant/components/lookin/media_player.py index 9d689b1d241..fa3a3622a31 100644 --- a/homeassistant/components/lookin/media_player.py +++ b/homeassistant/components/lookin/media_player.py @@ -8,14 +8,7 @@ from aiolookin import Remote from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, -) -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON, STATE_STANDBY, Platform @@ -35,13 +28,13 @@ _TYPE_TO_DEVICE_CLASS = { } _FUNCTION_NAME_TO_FEATURE = { - "power": SUPPORT_TURN_OFF, - "poweron": SUPPORT_TURN_ON, - "poweroff": SUPPORT_TURN_OFF, - "mute": SUPPORT_VOLUME_MUTE, - "volup": SUPPORT_VOLUME_STEP, - "chup": SUPPORT_NEXT_TRACK, - "chdown": SUPPORT_PREVIOUS_TRACK, + "power": MediaPlayerEntityFeature.TURN_OFF, + "poweron": MediaPlayerEntityFeature.TURN_ON, + "poweroff": MediaPlayerEntityFeature.TURN_OFF, + "mute": MediaPlayerEntityFeature.VOLUME_MUTE, + "volup": MediaPlayerEntityFeature.VOLUME_STEP, + "chup": MediaPlayerEntityFeature.NEXT_TRACK, + "chdown": MediaPlayerEntityFeature.PREVIOUS_TRACK, } diff --git a/homeassistant/components/lookin/translations/hu.json b/homeassistant/components/lookin/translations/hu.json index ab18b579bd4..b6db30f8d99 100644 --- a/homeassistant/components/lookin/translations/hu.json +++ b/homeassistant/components/lookin/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, @@ -15,7 +15,7 @@ "step": { "device_name": { "data": { - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" } }, "discovery_confirm": { diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index c02a65cc425..377ba33f171 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -69,12 +69,10 @@ class LovelaceConfig(ABC): async def async_save(self, config): """Save config.""" - # pylint: disable=no-self-use raise HomeAssistantError("Not supported") async def async_delete(self): """Delete config.""" - # pylint: disable=no-self-use raise HomeAssistantError("Not supported") @callback diff --git a/homeassistant/components/luftdaten/translations/hu.json b/homeassistant/components/luftdaten/translations/hu.json index f7a241533da..7439546f796 100644 --- a/homeassistant/components/luftdaten/translations/hu.json +++ b/homeassistant/components/luftdaten/translations/hu.json @@ -9,7 +9,7 @@ "user": { "data": { "show_on_map": "Megjelen\u00edt\u00e9s a t\u00e9rk\u00e9pen", - "station_id": "Luftdaten \u00e9rz\u00e9kel\u0151 ID" + "station_id": "\u00c9rz\u00e9kel\u0151 azonos\u00edt\u00f3" }, "title": "Luftdaten be\u00e1ll\u00edt\u00e1sa" } diff --git a/homeassistant/components/luftdaten/translations/pt-BR.json b/homeassistant/components/luftdaten/translations/pt-BR.json index b4cdaf000ab..82b1f09735b 100644 --- a/homeassistant/components/luftdaten/translations/pt-BR.json +++ b/homeassistant/components/luftdaten/translations/pt-BR.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "show_on_map": "Mostrar no mapa?", + "show_on_map": "Mostrar no mapa", "station_id": "ID do Sensor Luftdaten" }, "title": "Definir Luftdaten" diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py index 3a36795db2f..812225ea407 100644 --- a/homeassistant/components/lupusec/alarm_control_panel.py +++ b/homeassistant/components/lupusec/alarm_control_panel.py @@ -3,10 +3,9 @@ from __future__ import annotations from datetime import timedelta -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -45,6 +44,11 @@ def setup_platform( class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity): """An alarm_control_panel implementation for Lupusec.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + @property def icon(self): """Return the icon.""" @@ -65,11 +69,6 @@ class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity): state = None return state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - def alarm_arm_away(self, code=None): """Send arm away command.""" self._device.set_away() diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index 701cbfbafaa..45b7751aa7c 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -5,10 +5,8 @@ import logging from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverEntity, + CoverEntityFeature, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -37,10 +35,11 @@ def setup_platform( class LutronCover(LutronDevice, CoverEntity): """Representation of a Lutron shade.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) @property def is_closed(self): diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index 9fa677019e6..52ff2d7843c 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -1,11 +1,7 @@ """Support for Lutron lights.""" from __future__ import annotations -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -41,16 +37,14 @@ def to_hass_level(level): class LutronLight(LutronDevice, LightEntity): """Representation of a Lutron Light, including dimmable.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, area_name, lutron_device, controller): """Initialize the light.""" self._prev_brightness = None super().__init__(area_name, lutron_device, controller) - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - @property def brightness(self): """Return the brightness of the light.""" diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index d0561bc47ab..bb8f94f3abe 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -294,6 +294,8 @@ async def async_unload_entry( class LutronCasetaDevice(Entity): """Common base class for all Lutron Caseta devices.""" + _attr_should_poll = False + def __init__(self, device, bridge, bridge_device): """Set up the base class. @@ -304,6 +306,18 @@ class LutronCasetaDevice(Entity): self._device = device self._smartbridge = bridge self._bridge_device = bridge_device + info = DeviceInfo( + identifiers={(DOMAIN, self.serial)}, + manufacturer=MANUFACTURER, + model=f"{device['model']} ({device['type']})", + name=self.name, + via_device=(DOMAIN, self._bridge_device["serial"]), + configuration_url="https://device-login.lutron.com", + ) + area, _ = _area_and_name_from_name(device["name"]) + if area != UNASSIGNED_AREA: + info[ATTR_SUGGESTED_AREA] = area + self._attr_device_info = info async def async_added_to_hass(self): """Register callbacks.""" @@ -329,28 +343,7 @@ class LutronCasetaDevice(Entity): """Return the unique ID of the device (serial).""" return str(self.serial) - @property - def device_info(self) -> DeviceInfo: - """Return the device info.""" - device = self._device - info = DeviceInfo( - identifiers={(DOMAIN, self.serial)}, - manufacturer=MANUFACTURER, - model=f"{device['model']} ({device['type']})", - name=self.name, - via_device=(DOMAIN, self._bridge_device["serial"]), - configuration_url="https://device-login.lutron.com", - ) - area, _ = _area_and_name_from_name(device["name"]) - if area != UNASSIGNED_AREA: - info[ATTR_SUGGESTED_AREA] = area - @property def extra_state_attributes(self): """Return the state attributes.""" return {"device_id": self.device_id, "zone_id": self._device["zone"]} - - @property - def should_poll(self): - """No polling needed.""" - return False diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index 68a7c16db1d..61c9c42a1b0 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -4,12 +4,9 @@ import logging from homeassistant.components.cover import ( ATTR_POSITION, DOMAIN, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -47,10 +44,12 @@ async def async_setup_entry( class LutronCasetaCover(LutronCasetaDevice, CoverEntity): """Representation of a Lutron shade.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + ) @property def is_closed(self): diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index cb00fa6f2c8..00236405735 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -5,7 +5,7 @@ import logging from pylutron_caseta import FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_OFF -from homeassistant.components.fan import DOMAIN, SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import DOMAIN, FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -49,6 +49,8 @@ async def async_setup_entry( class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Representation of a Lutron Caseta fan. Including Fan Speed.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + @property def percentage(self) -> int | None: """Return the current speed percentage.""" @@ -65,11 +67,6 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Return the number of speeds the fan supports.""" return len(ORDERED_NAMED_FAN_SPEEDS) - @property - def supported_features(self) -> int: - """Flag supported features. Speed Only.""" - return SUPPORT_SET_SPEED - async def async_turn_on( self, percentage: int = None, diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index d60f75a4ebc..cba4450bf27 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -6,9 +6,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, DOMAIN, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -56,10 +56,9 @@ async def async_setup_entry( class LutronCasetaLight(LutronCasetaDevice, LightEntity): """Representation of a Lutron Light, including dimmable.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = LightEntityFeature.TRANSITION @property def brightness(self): diff --git a/homeassistant/components/lutron_caseta/translations/pl.json b/homeassistant/components/lutron_caseta/translations/pl.json index bc4f9d61f39..47e6a07e146 100644 --- a/homeassistant/components/lutron_caseta/translations/pl.json +++ b/homeassistant/components/lutron_caseta/translations/pl.json @@ -23,7 +23,7 @@ "host": "Nazwa hosta lub adres IP" }, "description": "Wprowad\u017a adres IP urz\u0105dzenia", - "title": "Po\u0142\u0105cz si\u0119 automatycznie z mostkiem" + "title": "Automatyczne po\u0142\u0105czenie z mostkiem" } } }, diff --git a/homeassistant/components/lw12wifi/light.py b/homeassistant/components/lw12wifi/light.py index 86d30fd737a..deb721da29e 100644 --- a/homeassistant/components/lw12wifi/light.py +++ b/homeassistant/components/lw12wifi/light.py @@ -12,11 +12,9 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_TRANSITION, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant @@ -59,6 +57,10 @@ def setup_platform( class LW12WiFi(LightEntity): """LW-12 WiFi LED Controller.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + _attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION + def __init__(self, name, lw12_light): """Initialise LW-12 WiFi LED Controller. @@ -71,10 +73,6 @@ class LW12WiFi(LightEntity): self._effect = None self._rgb_color = [255, 255, 255] self._brightness = 255 - # Setup feature list - self._supported_features = ( - SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR | SUPPORT_TRANSITION - ) @property def name(self): @@ -103,11 +101,6 @@ class LW12WiFi(LightEntity): """Return true if light is on.""" return self._state - @property - def supported_features(self): - """Return a list of supported features.""" - return self._supported_features - @property def effect_list(self): """Return a list of available effects. diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 56b5b6fd022..b5c1fb05efa 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -13,17 +13,9 @@ from homeassistant.components.climate import ClimateEntity, ClimateEntityDescrip from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE @@ -49,9 +41,14 @@ _LOGGER = logging.getLogger(__name__) # Only LCC models support presets SUPPORT_FLAGS_LCC = ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE_RANGE + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE +) +SUPPORT_FLAGS_TCC = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) -SUPPORT_FLAGS_TCC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE LYRIC_HVAC_ACTION_OFF = "EquipmentOff" LYRIC_HVAC_ACTION_HEAT = "Heat" @@ -63,23 +60,23 @@ LYRIC_HVAC_MODE_COOL = "Cool" LYRIC_HVAC_MODE_HEAT_COOL = "Auto" LYRIC_HVAC_MODES = { - HVAC_MODE_OFF: LYRIC_HVAC_MODE_OFF, - HVAC_MODE_HEAT: LYRIC_HVAC_MODE_HEAT, - HVAC_MODE_COOL: LYRIC_HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL: LYRIC_HVAC_MODE_HEAT_COOL, + HVACMode.OFF: LYRIC_HVAC_MODE_OFF, + HVACMode.HEAT: LYRIC_HVAC_MODE_HEAT, + HVACMode.COOL: LYRIC_HVAC_MODE_COOL, + HVACMode.HEAT_COOL: LYRIC_HVAC_MODE_HEAT_COOL, } HVAC_MODES = { - LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF, - LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT, - LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL, - LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, + LYRIC_HVAC_MODE_OFF: HVACMode.OFF, + LYRIC_HVAC_MODE_HEAT: HVACMode.HEAT, + LYRIC_HVAC_MODE_COOL: HVACMode.COOL, + LYRIC_HVAC_MODE_HEAT_COOL: HVACMode.HEAT_COOL, } HVAC_ACTIONS = { - LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF, - LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT, - LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL, + LYRIC_HVAC_ACTION_OFF: HVACAction.OFF, + LYRIC_HVAC_ACTION_HEAT: HVACAction.HEATING, + LYRIC_HVAC_ACTION_COOL: HVACAction.COOLING, } SERVICE_HOLD_TIME = "set_hold_time" @@ -146,20 +143,20 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): self._temperature_unit = temperature_unit # Setup supported hvac modes - self._hvac_modes = [HVAC_MODE_OFF] + self._attr_hvac_modes = [HVACMode.OFF] # Add supported lyric thermostat features if LYRIC_HVAC_MODE_HEAT in device.allowedModes: - self._hvac_modes.append(HVAC_MODE_HEAT) + self._attr_hvac_modes.append(HVACMode.HEAT) if LYRIC_HVAC_MODE_COOL in device.allowedModes: - self._hvac_modes.append(HVAC_MODE_COOL) + self._attr_hvac_modes.append(HVACMode.COOL) if ( LYRIC_HVAC_MODE_HEAT in device.allowedModes and LYRIC_HVAC_MODE_COOL in device.allowedModes ): - self._hvac_modes.append(HVAC_MODE_HEAT_COOL) + self._attr_hvac_modes.append(HVACMode.HEAT_COOL) super().__init__( coordinator, @@ -189,33 +186,28 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return self.device.indoorTemperature @property - def hvac_action(self) -> str: + def hvac_action(self) -> HVACAction: """Return the current hvac action.""" action = HVAC_ACTIONS.get(self.device.operationStatus.mode, None) - if action == CURRENT_HVAC_OFF and self.hvac_mode != HVAC_MODE_OFF: - action = CURRENT_HVAC_IDLE + if action == HVACAction.OFF and self.hvac_mode != HVACMode.OFF: + action = HVACAction.IDLE return action @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return the hvac mode.""" return HVAC_MODES[self.device.changeableValues.mode] - @property - def hvac_modes(self) -> list[str]: - """List of available hvac modes.""" - return self._hvac_modes - @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" device = self.device if ( device.changeableValues.autoChangeoverActive - or HVAC_MODES[device.changeableValues.mode] == HVAC_MODE_OFF + or HVAC_MODES[device.changeableValues.mode] == HVACMode.OFF ): return None - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return device.changeableValues.coolSetpoint return device.changeableValues.heatSetpoint @@ -225,7 +217,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): device = self.device if ( not device.changeableValues.autoChangeoverActive - or HVAC_MODES[device.changeableValues.mode] == HVAC_MODE_OFF + or HVAC_MODES[device.changeableValues.mode] == HVACMode.OFF ): return None return device.changeableValues.coolSetpoint @@ -236,7 +228,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): device = self.device if ( not device.changeableValues.autoChangeoverActive - or HVAC_MODES[device.changeableValues.mode] == HVAC_MODE_OFF + or HVAC_MODES[device.changeableValues.mode] == HVACMode.OFF ): return None return device.changeableValues.heatSetpoint @@ -275,7 +267,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: return device = self.device @@ -303,7 +295,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): temp = kwargs.get(ATTR_TEMPERATURE) _LOGGER.debug("Set temperature: %s", temp) try: - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: await self._update_thermostat( self.location, device, coolSetpoint=temp ) @@ -315,7 +307,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): _LOGGER.error(exception) await self.coordinator.async_refresh() - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set hvac mode.""" _LOGGER.debug("HVAC mode: %s", hvac_mode) try: @@ -324,7 +316,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): # Auto briefly and then reverts to Off (perhaps related to heatCoolMode). This is the # behavior that happens with the native app as well, so likely a bug in the api itself - if HVAC_MODES[self.device.changeableValues.mode] == HVAC_MODE_OFF: + if HVAC_MODES[self.device.changeableValues.mode] == HVACMode.OFF: _LOGGER.debug( "HVAC mode passed to lyric: %s", HVAC_MODES[LYRIC_HVAC_MODE_COOL], diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json index 146a3397297..da60b046eb7 100644 --- a/homeassistant/components/lyric/manifest.json +++ b/homeassistant/components/lyric/manifest.json @@ -3,7 +3,7 @@ "name": "Honeywell Lyric", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/lyric", - "dependencies": ["http"], + "dependencies": ["auth"], "requirements": ["aiolyric==1.0.8"], "codeowners": ["@timmo001"], "quality_scale": "silver", diff --git a/homeassistant/components/lyric/translations/hu.json b/homeassistant/components/lyric/translations/hu.json index 7586310c8a7..b7eac97525c 100644 --- a/homeassistant/components/lyric/translations/hu.json +++ b/homeassistant/components/lyric/translations/hu.json @@ -10,7 +10,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "reauth_confirm": { "description": "A Lyric integr\u00e1ci\u00f3nak \u00fajra hiteles\u00edtenie kell a fi\u00f3kj\u00e1t.", diff --git a/homeassistant/components/lyric/translations/it.json b/homeassistant/components/lyric/translations/it.json index c544598079e..6fb3fb44275 100644 --- a/homeassistant/components/lyric/translations/it.json +++ b/homeassistant/components/lyric/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "create_entry": { diff --git a/homeassistant/components/mailgun/translations/ja.json b/homeassistant/components/mailgun/translations/ja.json index 58818dd99de..34c6ced3f38 100644 --- a/homeassistant/components/mailgun/translations/ja.json +++ b/homeassistant/components/mailgun/translations/ja.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, "create_entry": { - "default": "Home Assistant\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u3059\u308b\u306b\u306f\u3001[Webhooks with Mailgun]({mailgun_url})\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u4ee5\u4e0b\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n- URL: `{webhook_url}`\n- Method(\u65b9\u5f0f): POST\n- Content Type: application/x-www-fjsorm-urlencoded\n\n\u53d7\u4fe1\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8]({docs_url})\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + "default": "Home Assistant\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u3059\u308b\u306b\u306f\u3001[Webhooks with Mailgun]({mailgun_url})\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u4ee5\u4e0b\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n- URL: `{webhook_url}`\n- Method(\u65b9\u5f0f): POST\n- Content Type(\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e): application/x-www-fjsorm-urlencoded\n\n\u53d7\u4fe1\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8]({docs_url})\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "step": { "user": { diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 8f86f3cdbdf..dd347336d9e 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -10,12 +10,7 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_CUSTOM_BYPASS, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_ARM_VACATION, - SUPPORT_ALARM_TRIGGER, + AlarmControlPanelEntityFeature, ) from homeassistant.const import ( CONF_ARMING_TIME, @@ -262,12 +257,12 @@ class ManualAlarm(alarm.AlarmControlPanelEntity, RestoreEntity): def supported_features(self) -> int: """Return the list of supported features.""" return ( - SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_ARM_VACATION - | SUPPORT_ALARM_TRIGGER - | SUPPORT_ALARM_ARM_CUSTOM_BYPASS + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.ARM_VACATION + | AlarmControlPanelEntityFeature.TRIGGER + | AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS ) @property @@ -299,8 +294,8 @@ class ManualAlarm(alarm.AlarmControlPanelEntity, RestoreEntity): if self._code is None: return None if isinstance(self._code, str) and re.search("^\\d+$", self._code): - return alarm.FORMAT_NUMBER - return alarm.FORMAT_TEXT + return alarm.CodeFormat.NUMBER + return alarm.CodeFormat.TEXT @property def code_arm_required(self): diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index c854e11c0dc..730d7ae1f9e 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -10,12 +10,7 @@ import voluptuous as vol from homeassistant.components import mqtt import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_TRIGGER, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.const import ( CONF_CODE, CONF_DELAY_TIME, @@ -209,6 +204,13 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): A trigger_time of zero disables the alarm_trigger service. """ + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.TRIGGER + ) + def __init__( self, hass, @@ -293,16 +295,6 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return ( - SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_TRIGGER - ) - @property def _active_state(self): """Get the current state.""" @@ -327,8 +319,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): if self._code is None: return None if isinstance(self._code, str) and re.search("^\\d+$", self._code): - return alarm.FORMAT_NUMBER - return alarm.FORMAT_TEXT + return alarm.CodeFormat.NUMBER + return alarm.CodeFormat.TEXT @property def code_arm_required(self): diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index 03772630a9e..47646a586ca 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -362,7 +362,7 @@ class MatrixBot: for target_room in target_rooms: try: room = self._join_or_get_room(target_room) - room.send_image(mxc, img) + room.send_image(mxc, img, mimetype=content_type) except MatrixRequestError as ex: _LOGGER.error( "Unable to deliver message to room '%s': %d, %s", diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index cb443247810..c5d04ae599c 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -13,19 +13,14 @@ from maxcube.device import ( from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -50,8 +45,6 @@ MIN_TEMPERATURE = 5.0 # Largest Value without fully opening MAX_TEMPERATURE = 30.0 -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - def setup_platform( hass: HomeAssistant, @@ -73,17 +66,20 @@ def setup_platform( class MaxCubeClimate(ClimateEntity): """MAX! Cube ClimateEntity.""" + _attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.HEAT] + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, handler, device): """Initialize MAX! Cube ClimateEntity.""" room = handler.cube.room_by_id(device.room_id) self._attr_name = f"{room.name} {device.name}" self._cubehandle = handler self._device = device - self._attr_supported_features = SUPPORT_FLAGS self._attr_should_poll = True self._attr_unique_id = self._device.serial self._attr_temperature_unit = TEMP_CELSIUS - self._attr_hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_HEAT] self._attr_preset_modes = [ PRESET_NONE, PRESET_BOOST, @@ -98,7 +94,7 @@ class MaxCubeClimate(ClimateEntity): """Return the minimum temperature.""" temp = self._device.min_temperature or MIN_TEMPERATURE # OFF_TEMPERATURE (always off) a is valid temperature to maxcube but not to Home Assistant. - # We use HVAC_MODE_OFF instead to represent a turned off thermostat. + # We use HVACMode.OFF instead to represent a turned off thermostat. return max(temp, MIN_TEMPERATURE) @property @@ -112,27 +108,27 @@ class MaxCubeClimate(ClimateEntity): return self._device.actual_temperature @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation mode.""" mode = self._device.mode if mode in (MAX_DEVICE_MODE_AUTOMATIC, MAX_DEVICE_MODE_BOOST): - return HVAC_MODE_AUTO + return HVACMode.AUTO if ( mode == MAX_DEVICE_MODE_MANUAL and self._device.target_temperature == OFF_TEMPERATURE ): - return HVAC_MODE_OFF + return HVACMode.OFF - return HVAC_MODE_HEAT + return HVACMode.HEAT - def set_hvac_mode(self, hvac_mode: str): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: self._set_target(MAX_DEVICE_MODE_MANUAL, OFF_TEMPERATURE) - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: temp = max(self._device.target_temperature, self.min_temp) self._set_target(MAX_DEVICE_MODE_MANUAL, temp) - elif hvac_mode == HVAC_MODE_AUTO: + elif hvac_mode == HVACMode.AUTO: self._set_target(MAX_DEVICE_MODE_AUTOMATIC, None) else: raise ValueError(f"unsupported HVAC mode {hvac_mode}") @@ -157,7 +153,7 @@ class MaxCubeClimate(ClimateEntity): _LOGGER.error("Setting HVAC mode failed") @property - def hvac_action(self): + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" valve = 0 @@ -175,11 +171,9 @@ class MaxCubeClimate(ClimateEntity): # Assume heating when valve is open if valve > 0: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING - return ( - CURRENT_HVAC_OFF if self.hvac_mode == HVAC_MODE_OFF else CURRENT_HVAC_IDLE - ) + return HVACAction.OFF if self.hvac_mode == HVACMode.OFF else HVACAction.IDLE @property def target_temperature(self): diff --git a/homeassistant/components/mazda/translations/ca.json b/homeassistant/components/mazda/translations/ca.json index 17ef370b007..2289ba3986c 100644 --- a/homeassistant/components/mazda/translations/ca.json +++ b/homeassistant/components/mazda/translations/ca.json @@ -17,10 +17,8 @@ "password": "Contrasenya", "region": "Regi\u00f3" }, - "description": "Introdueix el correu electr\u00f2nic i la contrasenya que utilitzes per iniciar sessi\u00f3 a l'aplicaci\u00f3 de m\u00f2bil MyMazda.", - "title": "Serveis connectats de Mazda - Afegeix un compte" + "description": "Introdueix el correu electr\u00f2nic i la contrasenya que utilitzes per iniciar sessi\u00f3 a l'aplicaci\u00f3 de m\u00f2bil MyMazda." } } - }, - "title": "Serveis connectats de Mazda" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/de.json b/homeassistant/components/mazda/translations/de.json index 58409ec9b8a..ba116a6fe2c 100644 --- a/homeassistant/components/mazda/translations/de.json +++ b/homeassistant/components/mazda/translations/de.json @@ -17,10 +17,8 @@ "password": "Passwort", "region": "Region" }, - "description": "Bitte gib die E-Mail-Adresse und das Passwort ein, die du f\u00fcr die Anmeldung bei der MyMazda Mobile App verwendest.", - "title": "Mazda Connected Services - Konto hinzuf\u00fcgen" + "description": "Bitte gib die E-Mail-Adresse und das Passwort ein, die du f\u00fcr die Anmeldung bei der MyMazda Mobile App verwendest." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/el.json b/homeassistant/components/mazda/translations/el.json index c95e8ad4747..7fbf538de63 100644 --- a/homeassistant/components/mazda/translations/el.json +++ b/homeassistant/components/mazda/translations/el.json @@ -17,10 +17,8 @@ "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae MyMazda \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac.", - "title": "Mazda Connected Services - \u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd" + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae MyMazda \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/en.json b/homeassistant/components/mazda/translations/en.json index b483947aaa0..9130a1b6334 100644 --- a/homeassistant/components/mazda/translations/en.json +++ b/homeassistant/components/mazda/translations/en.json @@ -17,10 +17,8 @@ "password": "Password", "region": "Region" }, - "description": "Please enter the email address and password you use to log into the MyMazda mobile app.", - "title": "Mazda Connected Services - Add Account" + "description": "Please enter the email address and password you use to log into the MyMazda mobile app." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/es.json b/homeassistant/components/mazda/translations/es.json index a9ffa26787c..f0ba0f4da49 100644 --- a/homeassistant/components/mazda/translations/es.json +++ b/homeassistant/components/mazda/translations/es.json @@ -17,10 +17,8 @@ "password": "Contrase\u00f1a", "region": "Regi\u00f3n" }, - "description": "Introduce la direcci\u00f3n de correo electr\u00f3nico y la contrase\u00f1a que utilizas para iniciar sesi\u00f3n en la aplicaci\u00f3n m\u00f3vil MyMazda.", - "title": "Servicios Conectados de Mazda - A\u00f1adir cuenta" + "description": "Introduce la direcci\u00f3n de correo electr\u00f3nico y la contrase\u00f1a que utilizas para iniciar sesi\u00f3n en la aplicaci\u00f3n m\u00f3vil MyMazda." } } - }, - "title": "Servicios Conectados de Mazda" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/et.json b/homeassistant/components/mazda/translations/et.json index 39ead99765c..1b6cac11093 100644 --- a/homeassistant/components/mazda/translations/et.json +++ b/homeassistant/components/mazda/translations/et.json @@ -17,10 +17,8 @@ "password": "Salas\u00f5na", "region": "Piirkond" }, - "description": "Sisesta e-posti aadress ja salas\u00f5na mida kasutad MyMazda mobiilirakendusse sisselogimiseks.", - "title": "Mazda Connected Services - lisa konto" + "description": "Sisesta e-posti aadress ja salas\u00f5na mida kasutad MyMazda mobiilirakendusse sisselogimiseks." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/fr.json b/homeassistant/components/mazda/translations/fr.json index 8024852de46..d42ab5453c4 100644 --- a/homeassistant/components/mazda/translations/fr.json +++ b/homeassistant/components/mazda/translations/fr.json @@ -17,10 +17,8 @@ "password": "Mot de passe", "region": "R\u00e9gion" }, - "description": "Veuillez saisir l'adresse e-mail et le mot de passe que vous utilisez pour vous connecter \u00e0 l'application mobile MyMazda.", - "title": "Services connect\u00e9s Mazda - Ajouter un compte" + "description": "Veuillez saisir l'adresse e-mail et le mot de passe que vous utilisez pour vous connecter \u00e0 l'application mobile MyMazda." } } - }, - "title": "Services connect\u00e9s Mazda" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/hu.json b/homeassistant/components/mazda/translations/hu.json index ec62b456021..42afc763687 100644 --- a/homeassistant/components/mazda/translations/hu.json +++ b/homeassistant/components/mazda/translations/hu.json @@ -17,10 +17,8 @@ "password": "Jelsz\u00f3", "region": "R\u00e9gi\u00f3" }, - "description": "K\u00e9rj\u00fck, adja meg azt az e-mail c\u00edmet \u00e9s jelsz\u00f3t, amelyet a MyMazda mobilalkalmaz\u00e1sba val\u00f3 bejelentkez\u00e9shez haszn\u00e1lt.", - "title": "Mazda Connected Services - Fi\u00f3k hozz\u00e1ad\u00e1sa" + "description": "K\u00e9rj\u00fck, adja meg azt az e-mail c\u00edmet \u00e9s jelsz\u00f3t, amelyet a MyMazda mobilalkalmaz\u00e1sba val\u00f3 bejelentkez\u00e9shez haszn\u00e1lt." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/id.json b/homeassistant/components/mazda/translations/id.json index fdcc736162e..cd4f73f23f6 100644 --- a/homeassistant/components/mazda/translations/id.json +++ b/homeassistant/components/mazda/translations/id.json @@ -17,10 +17,8 @@ "password": "Kata Sandi", "region": "Wilayah" }, - "description": "Masukkan alamat email dan kata sandi yang digunakan untuk masuk ke aplikasi seluler MyMazda.", - "title": "Mazda Connected Services - Tambahkan Akun" + "description": "Masukkan alamat email dan kata sandi yang digunakan untuk masuk ke aplikasi seluler MyMazda." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/it.json b/homeassistant/components/mazda/translations/it.json index 21d003b030b..920e2b5ce3c 100644 --- a/homeassistant/components/mazda/translations/it.json +++ b/homeassistant/components/mazda/translations/it.json @@ -17,10 +17,8 @@ "password": "Password", "region": "Area geografica" }, - "description": "Inserisci l'indirizzo email e la password che utilizzi per accedere all'app mobile MyMazda.", - "title": "Mazda Connected Services - Aggiungi account" + "description": "Inserisci l'indirizzo email e la password che utilizzi per accedere all'app mobile MyMazda." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/ja.json b/homeassistant/components/mazda/translations/ja.json index 3bf5b7f88b3..690f5a097de 100644 --- a/homeassistant/components/mazda/translations/ja.json +++ b/homeassistant/components/mazda/translations/ja.json @@ -17,10 +17,8 @@ "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", "region": "\u30ea\u30fc\u30b8\u30e7\u30f3" }, - "description": "MyMazda\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u306b\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u969b\u306b\u4f7f\u7528\u3059\u308b\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u3068\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "\u30de\u30c4\u30c0 \u30b3\u30cd\u30af\u30c6\u30c3\u30c9\u30b5\u30fc\u30d3\u30b9 - \u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u8ffd\u52a0" + "description": "MyMazda\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u306b\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u969b\u306b\u4f7f\u7528\u3059\u308b\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u3068\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } - }, - "title": "\u30de\u30c4\u30c0 \u30b3\u30cd\u30af\u30c6\u30c3\u30c9\u30b5\u30fc\u30d3\u30b9" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/ko.json b/homeassistant/components/mazda/translations/ko.json index 6023a053a7e..b95170c6bbd 100644 --- a/homeassistant/components/mazda/translations/ko.json +++ b/homeassistant/components/mazda/translations/ko.json @@ -17,10 +17,8 @@ "password": "\ube44\ubc00\ubc88\ud638", "region": "\uc9c0\uc5ed" }, - "description": "MyMazda \ubaa8\ubc14\uc77c \uc571\uc5d0 \ub85c\uadf8\uc778\ud558\uae30 \uc704\ud574 \uc0ac\uc6a9\ud558\ub294 \uc774\uba54\uc77c \uc8fc\uc18c\uc640 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "Mazda Connected Services - \uacc4\uc815 \ucd94\uac00\ud558\uae30" + "description": "MyMazda \ubaa8\ubc14\uc77c \uc571\uc5d0 \ub85c\uadf8\uc778\ud558\uae30 \uc704\ud574 \uc0ac\uc6a9\ud558\ub294 \uc774\uba54\uc77c \uc8fc\uc18c\uc640 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index 3370fe29bb8..f7532b9fc45 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -17,10 +17,8 @@ "password": "Wachtwoord", "region": "Regio" }, - "description": "Voer het e-mailadres en wachtwoord in dat u gebruikt om in te loggen op de MyMazda mobiele app.", - "title": "Mazda Connected Services - Account toevoegen" + "description": "Voer het e-mailadres en wachtwoord in dat u gebruikt om in te loggen op de MyMazda mobiele app." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/no.json b/homeassistant/components/mazda/translations/no.json index 3f4db47d2b0..875e21e8c04 100644 --- a/homeassistant/components/mazda/translations/no.json +++ b/homeassistant/components/mazda/translations/no.json @@ -17,10 +17,8 @@ "password": "Passord", "region": "Region" }, - "description": "Vennligst skriv inn e-postadressen og passordet du bruker for \u00e5 logge p\u00e5 MyMazda-mobilappen.", - "title": "Mazda Connected Services - Legg til konto" + "description": "Vennligst skriv inn e-postadressen og passordet du bruker for \u00e5 logge p\u00e5 MyMazda-mobilappen." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/pl.json b/homeassistant/components/mazda/translations/pl.json index fdd1a8c5ce9..d4aea82b6c1 100644 --- a/homeassistant/components/mazda/translations/pl.json +++ b/homeassistant/components/mazda/translations/pl.json @@ -17,10 +17,8 @@ "password": "Has\u0142o", "region": "Region" }, - "description": "Wprowad\u017a adres e-mail i has\u0142o, kt\u00f3rych u\u017cywasz do logowania si\u0119 do aplikacji mobilnej MyMazda.", - "title": "Mazda Connected Services - Dodawanie konta" + "description": "Wprowad\u017a adres e-mail i has\u0142o, kt\u00f3rych u\u017cywasz do logowania si\u0119 do aplikacji mobilnej MyMazda." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/pt-BR.json b/homeassistant/components/mazda/translations/pt-BR.json index 7b28450bfd0..5dcfd6d59ab 100644 --- a/homeassistant/components/mazda/translations/pt-BR.json +++ b/homeassistant/components/mazda/translations/pt-BR.json @@ -17,10 +17,8 @@ "password": "Senha", "region": "Regi\u00e3o" }, - "description": "Digite o endere\u00e7o de e-mail e senha que voc\u00ea usa para entrar no aplicativo MyMazda.", - "title": "Mazda Connected Services - Adicionar conta" + "description": "Digite o endere\u00e7o de e-mail e senha que voc\u00ea usa para entrar no aplicativo MyMazda." } } - }, - "title": "Servi\u00e7os conectados Mazda" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/ru.json b/homeassistant/components/mazda/translations/ru.json index cf949416274..4d5082bcc49 100644 --- a/homeassistant/components/mazda/translations/ru.json +++ b/homeassistant/components/mazda/translations/ru.json @@ -17,10 +17,8 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "region": "\u0420\u0435\u0433\u0438\u043e\u043d" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 MyMazda.", - "title": "Mazda Connected Services" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 MyMazda." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/tr.json b/homeassistant/components/mazda/translations/tr.json index 6c574d1de0b..788cee66b00 100644 --- a/homeassistant/components/mazda/translations/tr.json +++ b/homeassistant/components/mazda/translations/tr.json @@ -17,10 +17,8 @@ "password": "Parola", "region": "B\u00f6lge" }, - "description": "L\u00fctfen MyMazda mobil uygulamas\u0131na giri\u015f yapmak i\u00e7in kulland\u0131\u011f\u0131n\u0131z e-posta adresini ve \u015fifreyi giriniz.", - "title": "Mazda Connected Services - Hesap Ekle" + "description": "L\u00fctfen MyMazda mobil uygulamas\u0131na giri\u015f yapmak i\u00e7in kulland\u0131\u011f\u0131n\u0131z e-posta adresini ve \u015fifreyi giriniz." } } - }, - "title": "Mazda Connected Services" + } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/zh-Hant.json b/homeassistant/components/mazda/translations/zh-Hant.json index 0c0e3f0fd8c..bb52f321766 100644 --- a/homeassistant/components/mazda/translations/zh-Hant.json +++ b/homeassistant/components/mazda/translations/zh-Hant.json @@ -17,10 +17,8 @@ "password": "\u5bc6\u78bc", "region": "\u5340\u57df" }, - "description": "\u8acb\u8f38\u5165\u767b\u5165MyMazda \u884c\u52d5 App \u4e4b Email \u5730\u5740\u8207\u5bc6\u78bc\u3002", - "title": "Mazda Connected \u670d\u52d9 - \u65b0\u589e\u5e33\u865f" + "description": "\u8acb\u8f38\u5165\u767b\u5165MyMazda \u884c\u52d5 App \u4e4b Email \u5730\u5740\u8207\u5bc6\u78bc\u3002" } } - }, - "title": "Mazda Connected \u670d\u52d9" + } } \ No newline at end of file diff --git a/homeassistant/components/meater/__init__.py b/homeassistant/components/meater/__init__.py new file mode 100644 index 00000000000..a722928a13f --- /dev/null +++ b/homeassistant/components/meater/__init__.py @@ -0,0 +1,90 @@ +"""The Meater Temperature Probe integration.""" +from datetime import timedelta +import logging + +import async_timeout +from meater import ( + AuthenticationError, + MeaterApi, + ServiceUnavailableError, + TooManyRequestsError, +) +from meater.MeaterApi import MeaterProbe + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +PLATFORMS = [Platform.SENSOR] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Meater Temperature Probe from a config entry.""" + # Store an API object to access + session = async_get_clientsession(hass) + meater_api = MeaterApi(session) + + # Add the credentials + try: + _LOGGER.debug("Authenticating with the Meater API") + await meater_api.authenticate( + entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD] + ) + except (ServiceUnavailableError, TooManyRequestsError) as err: + raise ConfigEntryNotReady from err + except AuthenticationError as err: + _LOGGER.error("Unable to authenticate with the Meater API: %s", err) + return False + + async def async_update_data() -> dict[str, MeaterProbe]: + """Fetch data from API endpoint.""" + try: + # Note: asyncio.TimeoutError and aiohttp.ClientError are already + # handled by the data update coordinator. + async with async_timeout.timeout(10): + devices: list[MeaterProbe] = await meater_api.get_all_devices() + except AuthenticationError as err: + raise UpdateFailed("The API call wasn't authenticated") from err + except TooManyRequestsError as err: + raise UpdateFailed( + "Too many requests have been made to the API, rate limiting is in place" + ) from err + + return {device.id: device for device in devices} + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="meater_api", + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=30), + ) + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN].setdefault("known_probes", set()) + + hass.data[DOMAIN][entry.entry_id] = { + "api": meater_api, + "coordinator": coordinator, + } + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/meater/config_flow.py b/homeassistant/components/meater/config_flow.py new file mode 100644 index 00000000000..1b1a8a0eca4 --- /dev/null +++ b/homeassistant/components/meater/config_flow.py @@ -0,0 +1,57 @@ +"""Config flow for Meater.""" +from meater import AuthenticationError, MeaterApi, ServiceUnavailableError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers import aiohttp_client + +from .const import DOMAIN + +FLOW_SCHEMA = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} +) + + +class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Meater Config Flow.""" + + async def async_step_user(self, user_input=None): + """Define the login user step.""" + if user_input is None: + return self.async_show_form( + step_id="user", + data_schema=FLOW_SCHEMA, + ) + + username: str = user_input[CONF_USERNAME] + await self.async_set_unique_id(username.lower()) + self._abort_if_unique_id_configured() + + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] + + session = aiohttp_client.async_get_clientsession(self.hass) + + api = MeaterApi(session) + errors = {} + + try: + await api.authenticate(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + except AuthenticationError: + errors["base"] = "invalid_auth" + except ServiceUnavailableError: + errors["base"] = "service_unavailable_error" + except Exception: # pylint: disable=broad-except + errors["base"] = "unknown_auth_error" + else: + return self.async_create_entry( + title="Meater", + data={"username": username, "password": password}, + ) + + return self.async_show_form( + step_id="user", + data_schema=FLOW_SCHEMA, + errors=errors, + ) diff --git a/homeassistant/components/meater/const.py b/homeassistant/components/meater/const.py new file mode 100644 index 00000000000..6b40aa18d59 --- /dev/null +++ b/homeassistant/components/meater/const.py @@ -0,0 +1,3 @@ +"""Constants for the Meater Temperature Probe integration.""" + +DOMAIN = "meater" diff --git a/homeassistant/components/meater/manifest.json b/homeassistant/components/meater/manifest.json new file mode 100644 index 00000000000..6df4f6939e1 --- /dev/null +++ b/homeassistant/components/meater/manifest.json @@ -0,0 +1,9 @@ +{ + "codeowners": ["@Sotolotl", "@emontnemery"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/meater", + "domain": "meater", + "iot_class": "cloud_polling", + "name": "Meater", + "requirements": ["meater-python==0.0.8"] +} diff --git a/homeassistant/components/meater/sensor.py b/homeassistant/components/meater/sensor.py new file mode 100644 index 00000000000..17e8db9e473 --- /dev/null +++ b/homeassistant/components/meater/sensor.py @@ -0,0 +1,220 @@ +"""The Meater Temperature Probe integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timedelta + +from meater.MeaterApi import MeaterProbe + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import TEMP_CELSIUS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) +from homeassistant.util import dt as dt_util + +from .const import DOMAIN + + +@dataclass +class MeaterSensorEntityDescriptionMixin: + """Mixin for MeaterSensorEntityDescription.""" + + available: Callable[[MeaterProbe | None], bool] + value: Callable[[MeaterProbe], datetime | float | str | None] + + +@dataclass +class MeaterSensorEntityDescription( + SensorEntityDescription, MeaterSensorEntityDescriptionMixin +): + """Describes meater sensor entity.""" + + +def _elapsed_time_to_timestamp(probe: MeaterProbe) -> datetime | None: + """Convert elapsed time to timestamp.""" + if not probe.cook: + return None + return dt_util.utcnow() - timedelta(seconds=probe.cook.time_elapsed) + + +def _remaining_time_to_timestamp(probe: MeaterProbe) -> datetime | None: + """Convert remaining time to timestamp.""" + if not probe.cook or probe.cook.time_remaining < 0: + return None + return dt_util.utcnow() + timedelta(probe.cook.time_remaining) + + +SENSOR_TYPES = ( + # Ambient temperature + MeaterSensorEntityDescription( + key="ambient", + device_class=SensorDeviceClass.TEMPERATURE, + name="Ambient", + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + available=lambda probe: probe is not None, + value=lambda probe: probe.ambient_temperature, + ), + # Internal temperature (probe tip) + MeaterSensorEntityDescription( + key="internal", + device_class=SensorDeviceClass.TEMPERATURE, + name="Internal", + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + available=lambda probe: probe is not None, + value=lambda probe: probe.internal_temperature, + ), + # Name of selected meat in user language or user given custom name + MeaterSensorEntityDescription( + key="cook_name", + name="Cooking", + available=lambda probe: probe is not None and probe.cook is not None, + value=lambda probe: probe.cook.name if probe.cook else None, + ), + # One of Not Started, Configured, Started, Ready For Resting, Resting, + # Slightly Underdone, Finished, Slightly Overdone, OVERCOOK!. Not translated. + MeaterSensorEntityDescription( + key="cook_state", + name="Cook state", + available=lambda probe: probe is not None and probe.cook is not None, + value=lambda probe: probe.cook.state if probe.cook else None, + ), + # Target temperature + MeaterSensorEntityDescription( + key="cook_target_temp", + device_class=SensorDeviceClass.TEMPERATURE, + name="Target", + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + available=lambda probe: probe is not None and probe.cook is not None, + value=lambda probe: probe.cook.target_temperature if probe.cook else None, + ), + # Peak temperature + MeaterSensorEntityDescription( + key="cook_peak_temp", + device_class=SensorDeviceClass.TEMPERATURE, + name="Peak", + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + available=lambda probe: probe is not None and probe.cook is not None, + value=lambda probe: probe.cook.peak_temperature if probe.cook else None, + ), + # Remaining time in seconds. When unknown/calculating default is used. Default: -1 + # Exposed as a TIMESTAMP sensor where the timestamp is current time + remaining time. + MeaterSensorEntityDescription( + key="cook_time_remaining", + device_class=SensorDeviceClass.TIMESTAMP, + name="Remaining time", + available=lambda probe: probe is not None and probe.cook is not None, + value=_remaining_time_to_timestamp, + ), + # Time since the start of cook in seconds. Default: 0. Exposed as a TIMESTAMP sensor + # where the timestamp is current time - elapsed time. + MeaterSensorEntityDescription( + key="cook_time_elapsed", + device_class=SensorDeviceClass.TIMESTAMP, + name="Elapsed time", + available=lambda probe: probe is not None and probe.cook is not None, + value=_elapsed_time_to_timestamp, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the entry.""" + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + "coordinator" + ] + + @callback + def async_update_data(): + """Handle updated data from the API endpoint.""" + if not coordinator.last_update_success: + return + + devices = coordinator.data + entities = [] + known_probes: set = hass.data[DOMAIN]["known_probes"] + + # Add entities for temperature probes which we've not yet seen + for dev in devices: + if dev.id in known_probes: + continue + + entities.extend( + [ + MeaterProbeTemperature(coordinator, dev, sensor_description) + for sensor_description in SENSOR_TYPES + ] + ) + known_probes.add(dev.id) + + async_add_entities(entities) + + return devices + + # Add a subscriber to the coordinator to discover new temperature probes + coordinator.async_add_listener(async_update_data) + + +class MeaterProbeTemperature( + SensorEntity, CoordinatorEntity[DataUpdateCoordinator[dict[str, MeaterProbe]]] +): + """Meater Temperature Sensor Entity.""" + + _attr_device_class = SensorDeviceClass.TEMPERATURE + _attr_native_unit_of_measurement = TEMP_CELSIUS + entity_description: MeaterSensorEntityDescription + + def __init__( + self, coordinator, device_id, description: MeaterSensorEntityDescription + ) -> None: + """Initialise the sensor.""" + super().__init__(coordinator) + self._attr_name = f"Meater Probe {description.name}" + self._attr_device_info = { + "identifiers": { + # Serial numbers are unique identifiers within a specific domain + (DOMAIN, device_id) + }, + "manufacturer": "Apption Labs", + "model": "Meater Probe", + "name": f"Meater Probe {device_id}", + } + self._attr_unique_id = f"{device_id}-{description.key}" + + self.device_id = device_id + self.entity_description = description + + @property + def native_value(self): + """Return the temperature of the probe.""" + if not (device := self.coordinator.data.get(self.device_id)): + return None + + return self.entity_description.value(device) + + @property + def available(self): + """Return if entity is available.""" + # See if the device was returned from the API. If not, it's offline + return ( + self.coordinator.last_update_success + and self.entity_description.available( + self.coordinator.data.get(self.device_id) + ) + ) diff --git a/homeassistant/components/meater/strings.json b/homeassistant/components/meater/strings.json new file mode 100644 index 00000000000..772e6afd080 --- /dev/null +++ b/homeassistant/components/meater/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "user": { + "description": "Set up your Meater Cloud account.", + "data": { + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + } + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown_auth_error": "[%key:common::config_flow::error::unknown%]", + "service_unavailable_error": "The API is currently unavailable, please try again later." + } + } +} diff --git a/homeassistant/components/meater/translations/bg.json b/homeassistant/components/meater/translations/bg.json new file mode 100644 index 00000000000..e5396fbc999 --- /dev/null +++ b/homeassistant/components/meater/translations/bg.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435", + "unknown_auth_error": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0430\u043a\u0430\u0443\u043d\u0442\u0430 \u0441\u0438 \u0432 Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/ca.json b/homeassistant/components/meater/translations/ca.json new file mode 100644 index 00000000000..0174767bc52 --- /dev/null +++ b/homeassistant/components/meater/translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "service_unavailable_error": "L'API no est\u00e0 disponible actualment, torna-ho a provar m\u00e9s tard.", + "unknown_auth_error": "Error inesperat" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Configura el teu compte de Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/cs.json b/homeassistant/components/meater/translations/cs.json new file mode 100644 index 00000000000..72c98504526 --- /dev/null +++ b/homeassistant/components/meater/translations/cs.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown_auth_error": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/de.json b/homeassistant/components/meater/translations/de.json new file mode 100644 index 00000000000..ed143e26b4c --- /dev/null +++ b/homeassistant/components/meater/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "service_unavailable_error": "Die API ist derzeit nicht verf\u00fcgbar. Bitte versuche es sp\u00e4ter erneut.", + "unknown_auth_error": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + }, + "description": "Richte dein Meater Cloud-Konto ein." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/el.json b/homeassistant/components/meater/translations/el.json new file mode 100644 index 00000000000..2d02110dd49 --- /dev/null +++ b/homeassistant/components/meater/translations/el.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "service_unavailable_error": "\u03a4\u03bf API \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", + "unknown_auth_error": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/en.json b/homeassistant/components/meater/translations/en.json new file mode 100644 index 00000000000..3ceb94bcef0 --- /dev/null +++ b/homeassistant/components/meater/translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Invalid authentication", + "service_unavailable_error": "The API is currently unavailable, please try again later.", + "unknown_auth_error": "Unexpected error" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + }, + "description": "Set up your Meater Cloud account." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/et.json b/homeassistant/components/meater/translations/et.json new file mode 100644 index 00000000000..55328467d3a --- /dev/null +++ b/homeassistant/components/meater/translations/et.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamine nurjus", + "service_unavailable_error": "API pole praegu saadaval, proovi hiljem uuesti.", + "unknown_auth_error": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Seadista Meater Cloudi konto." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/fr.json b/homeassistant/components/meater/translations/fr.json new file mode 100644 index 00000000000..9940cb6e24b --- /dev/null +++ b/homeassistant/components/meater/translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Authentification non valide", + "service_unavailable_error": "L'API est actuellement indisponible, veuillez r\u00e9essayer ult\u00e9rieurement.", + "unknown_auth_error": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Configurez votre compte Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/he.json b/homeassistant/components/meater/translations/he.json new file mode 100644 index 00000000000..f1376b2cf0d --- /dev/null +++ b/homeassistant/components/meater/translations/he.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "unknown_auth_error": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/hu.json b/homeassistant/components/meater/translations/hu.json new file mode 100644 index 00000000000..fd55bae3bdd --- /dev/null +++ b/homeassistant/components/meater/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "service_unavailable_error": "Az API jelenleg nem el\u00e9rhet\u0151, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg k\u00e9s\u0151bb \u00fajra.", + "unknown_auth_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "\u00c1ll\u00edtsa be a Meater Cloud fi\u00f3kj\u00e1t." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/id.json b/homeassistant/components/meater/translations/id.json new file mode 100644 index 00000000000..5d9e28c583c --- /dev/null +++ b/homeassistant/components/meater/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentikasi tidak valid", + "service_unavailable_error": "API saat ini tidak tersedia, harap coba lagi nanti.", + "unknown_auth_error": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Siapkan akun Meater Cloud Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/it.json b/homeassistant/components/meater/translations/it.json new file mode 100644 index 00000000000..fe3bc189ef6 --- /dev/null +++ b/homeassistant/components/meater/translations/it.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Autenticazione non valida", + "service_unavailable_error": "L'API non \u00e8 attualmente disponibile, riprova pi\u00f9 tardi.", + "unknown_auth_error": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Configura il tuo account Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/ja.json b/homeassistant/components/meater/translations/ja.json new file mode 100644 index 00000000000..db4dd2e9c6b --- /dev/null +++ b/homeassistant/components/meater/translations/ja.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "service_unavailable_error": "\u73fe\u5728API\u304c\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002\u5f8c\u3067\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", + "unknown_auth_error": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "password": "\u30d1\u30b9\u30ef\u30fc\u30c9", + "username": "\u30e6\u30fc\u30b6\u30fc\u540d" + }, + "description": "Meater Cloud\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/nl.json b/homeassistant/components/meater/translations/nl.json new file mode 100644 index 00000000000..a87175c7574 --- /dev/null +++ b/homeassistant/components/meater/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Ongeldige authenticatie", + "service_unavailable_error": "De API is momenteel niet beschikbaar, probeer het later opnieuw.", + "unknown_auth_error": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "Stel uw Meater Cloud-account in." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/no.json b/homeassistant/components/meater/translations/no.json new file mode 100644 index 00000000000..60bec4e6864 --- /dev/null +++ b/homeassistant/components/meater/translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Ugyldig godkjenning", + "service_unavailable_error": "API-en er for \u00f8yeblikket utilgjengelig, pr\u00f8v igjen senere.", + "unknown_auth_error": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Sett opp din Meater Cloud-konto." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/pl.json b/homeassistant/components/meater/translations/pl.json new file mode 100644 index 00000000000..1816069dd34 --- /dev/null +++ b/homeassistant/components/meater/translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "service_unavailable_error": "Interfejs API jest obecnie niedost\u0119pny, spr\u00f3buj ponownie p\u00f3\u017aniej.", + "unknown_auth_error": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Skonfiguruj swoje konto w Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/pt-BR.json b/homeassistant/components/meater/translations/pt-BR.json new file mode 100644 index 00000000000..103f3f76986 --- /dev/null +++ b/homeassistant/components/meater/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "service_unavailable_error": "A API est\u00e1 indispon\u00edvel no momento. Tente novamente mais tarde.", + "unknown_auth_error": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Usu\u00e1rio" + }, + "description": "Configure sua conta Meeater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/ru.json b/homeassistant/components/meater/translations/ru.json new file mode 100644 index 00000000000..a56f2f8ebbe --- /dev/null +++ b/homeassistant/components/meater/translations/ru.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "service_unavailable_error": "\u0412 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f API \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", + "unknown_auth_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Meater Cloud." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/tr.json b/homeassistant/components/meater/translations/tr.json new file mode 100644 index 00000000000..03f77d2ed51 --- /dev/null +++ b/homeassistant/components/meater/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "service_unavailable_error": "API \u015fu anda kullan\u0131lam\u0131yor, l\u00fctfen daha sonra tekrar deneyin.", + "unknown_auth_error": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Meater Cloud hesab\u0131n\u0131z\u0131 kurun." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meater/translations/zh-Hant.json b/homeassistant/components/meater/translations/zh-Hant.json new file mode 100644 index 00000000000..b04f4a54076 --- /dev/null +++ b/homeassistant/components/meater/translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "service_unavailable_error": "API \u76ee\u524d\u7121\u6cd5\u4f7f\u7528\uff0c\u8acb\u7a0d\u5019\u518d\u8a66\u3002", + "unknown_auth_error": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8a2d\u5b9a Meater Cloud \u5e33\u865f\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 1a2ea50d306..bf006e2bd4e 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -127,6 +127,7 @@ from .const import ( # noqa: F401 SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, + MediaPlayerEntityFeature, ) from .errors import BrowseError @@ -242,52 +243,61 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await component.async_setup(config) component.async_register_entity_service( - SERVICE_TURN_ON, {}, "async_turn_on", [SUPPORT_TURN_ON] + SERVICE_TURN_ON, {}, "async_turn_on", [MediaPlayerEntityFeature.TURN_ON] ) component.async_register_entity_service( - SERVICE_TURN_OFF, {}, "async_turn_off", [SUPPORT_TURN_OFF] + SERVICE_TURN_OFF, {}, "async_turn_off", [MediaPlayerEntityFeature.TURN_OFF] ) component.async_register_entity_service( - SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_TURN_OFF | SUPPORT_TURN_ON] + SERVICE_TOGGLE, + {}, + "async_toggle", + [MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.TURN_ON], ) component.async_register_entity_service( SERVICE_VOLUME_UP, {}, "async_volume_up", - [SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP], + [MediaPlayerEntityFeature.VOLUME_SET, MediaPlayerEntityFeature.VOLUME_STEP], ) component.async_register_entity_service( SERVICE_VOLUME_DOWN, {}, "async_volume_down", - [SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP], + [MediaPlayerEntityFeature.VOLUME_SET, MediaPlayerEntityFeature.VOLUME_STEP], ) component.async_register_entity_service( SERVICE_MEDIA_PLAY_PAUSE, {}, "async_media_play_pause", - [SUPPORT_PLAY | SUPPORT_PAUSE], + [MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE], ) component.async_register_entity_service( - SERVICE_MEDIA_PLAY, {}, "async_media_play", [SUPPORT_PLAY] + SERVICE_MEDIA_PLAY, {}, "async_media_play", [MediaPlayerEntityFeature.PLAY] ) component.async_register_entity_service( - SERVICE_MEDIA_PAUSE, {}, "async_media_pause", [SUPPORT_PAUSE] + SERVICE_MEDIA_PAUSE, {}, "async_media_pause", [MediaPlayerEntityFeature.PAUSE] ) component.async_register_entity_service( - SERVICE_MEDIA_STOP, {}, "async_media_stop", [SUPPORT_STOP] + SERVICE_MEDIA_STOP, {}, "async_media_stop", [MediaPlayerEntityFeature.STOP] ) component.async_register_entity_service( - SERVICE_MEDIA_NEXT_TRACK, {}, "async_media_next_track", [SUPPORT_NEXT_TRACK] + SERVICE_MEDIA_NEXT_TRACK, + {}, + "async_media_next_track", + [MediaPlayerEntityFeature.NEXT_TRACK], ) component.async_register_entity_service( SERVICE_MEDIA_PREVIOUS_TRACK, {}, "async_media_previous_track", - [SUPPORT_PREVIOUS_TRACK], + [MediaPlayerEntityFeature.PREVIOUS_TRACK], ) component.async_register_entity_service( - SERVICE_CLEAR_PLAYLIST, {}, "async_clear_playlist", [SUPPORT_CLEAR_PLAYLIST] + SERVICE_CLEAR_PLAYLIST, + {}, + "async_clear_playlist", + [MediaPlayerEntityFeature.CLEAR_PLAYLIST], ) component.async_register_entity_service( SERVICE_VOLUME_SET, @@ -298,7 +308,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _rename_keys(volume=ATTR_MEDIA_VOLUME_LEVEL), ), "async_set_volume_level", - [SUPPORT_VOLUME_SET], + [MediaPlayerEntityFeature.VOLUME_SET], ) component.async_register_entity_service( SERVICE_VOLUME_MUTE, @@ -309,7 +319,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _rename_keys(mute=ATTR_MEDIA_VOLUME_MUTED), ), "async_mute_volume", - [SUPPORT_VOLUME_MUTE], + [MediaPlayerEntityFeature.VOLUME_MUTE], ) component.async_register_entity_service( SERVICE_MEDIA_SEEK, @@ -320,25 +330,25 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _rename_keys(position=ATTR_MEDIA_SEEK_POSITION), ), "async_media_seek", - [SUPPORT_SEEK], + [MediaPlayerEntityFeature.SEEK], ) component.async_register_entity_service( SERVICE_JOIN, {vol.Required(ATTR_GROUP_MEMBERS): vol.All(cv.ensure_list, [cv.entity_id])}, "async_join_players", - [SUPPORT_GROUPING], + [MediaPlayerEntityFeature.GROUPING], ) component.async_register_entity_service( SERVICE_SELECT_SOURCE, {vol.Required(ATTR_INPUT_SOURCE): cv.string}, "async_select_source", - [SUPPORT_SELECT_SOURCE], + [MediaPlayerEntityFeature.SELECT_SOURCE], ) component.async_register_entity_service( SERVICE_SELECT_SOUND_MODE, {vol.Required(ATTR_SOUND_MODE): cv.string}, "async_select_sound_mode", - [SUPPORT_SELECT_SOUND_MODE], + [MediaPlayerEntityFeature.SELECT_SOUND_MODE], ) component.async_register_entity_service( SERVICE_PLAY_MEDIA, @@ -351,23 +361,23 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ), ), "async_play_media", - [SUPPORT_PLAY_MEDIA], + [MediaPlayerEntityFeature.PLAY_MEDIA], ) component.async_register_entity_service( SERVICE_SHUFFLE_SET, {vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean}, "async_set_shuffle", - [SUPPORT_SHUFFLE_SET], + [MediaPlayerEntityFeature.SHUFFLE_SET], ) component.async_register_entity_service( - SERVICE_UNJOIN, {}, "async_unjoin_player", [SUPPORT_GROUPING] + SERVICE_UNJOIN, {}, "async_unjoin_player", [MediaPlayerEntityFeature.GROUPING] ) component.async_register_entity_service( SERVICE_REPEAT_SET, {vol.Required(ATTR_MEDIA_REPEAT): vol.In(REPEAT_MODES)}, "async_set_repeat", - [SUPPORT_REPEAT_SET], + [MediaPlayerEntityFeature.REPEAT_SET], ) return True @@ -530,7 +540,6 @@ class MediaPlayerEntity(Entity): Must be implemented by integration. """ - # pylint: disable=no-self-use return None, None @property @@ -767,72 +776,74 @@ class MediaPlayerEntity(Entity): @property def support_play(self): """Boolean if play is supported.""" - return bool(self.supported_features & SUPPORT_PLAY) + return bool(self.supported_features & MediaPlayerEntityFeature.PLAY) @property def support_pause(self): """Boolean if pause is supported.""" - return bool(self.supported_features & SUPPORT_PAUSE) + return bool(self.supported_features & MediaPlayerEntityFeature.PAUSE) @property def support_stop(self): """Boolean if stop is supported.""" - return bool(self.supported_features & SUPPORT_STOP) + return bool(self.supported_features & MediaPlayerEntityFeature.STOP) @property def support_seek(self): """Boolean if seek is supported.""" - return bool(self.supported_features & SUPPORT_SEEK) + return bool(self.supported_features & MediaPlayerEntityFeature.SEEK) @property def support_volume_set(self): """Boolean if setting volume is supported.""" - return bool(self.supported_features & SUPPORT_VOLUME_SET) + return bool(self.supported_features & MediaPlayerEntityFeature.VOLUME_SET) @property def support_volume_mute(self): """Boolean if muting volume is supported.""" - return bool(self.supported_features & SUPPORT_VOLUME_MUTE) + return bool(self.supported_features & MediaPlayerEntityFeature.VOLUME_MUTE) @property def support_previous_track(self): """Boolean if previous track command supported.""" - return bool(self.supported_features & SUPPORT_PREVIOUS_TRACK) + return bool(self.supported_features & MediaPlayerEntityFeature.PREVIOUS_TRACK) @property def support_next_track(self): """Boolean if next track command supported.""" - return bool(self.supported_features & SUPPORT_NEXT_TRACK) + return bool(self.supported_features & MediaPlayerEntityFeature.NEXT_TRACK) @property def support_play_media(self): """Boolean if play media command supported.""" - return bool(self.supported_features & SUPPORT_PLAY_MEDIA) + return bool(self.supported_features & MediaPlayerEntityFeature.PLAY_MEDIA) @property def support_select_source(self): """Boolean if select source command supported.""" - return bool(self.supported_features & SUPPORT_SELECT_SOURCE) + return bool(self.supported_features & MediaPlayerEntityFeature.SELECT_SOURCE) @property def support_select_sound_mode(self): """Boolean if select sound mode command supported.""" - return bool(self.supported_features & SUPPORT_SELECT_SOUND_MODE) + return bool( + self.supported_features & MediaPlayerEntityFeature.SELECT_SOUND_MODE + ) @property def support_clear_playlist(self): """Boolean if clear playlist command supported.""" - return bool(self.supported_features & SUPPORT_CLEAR_PLAYLIST) + return bool(self.supported_features & MediaPlayerEntityFeature.CLEAR_PLAYLIST) @property def support_shuffle_set(self): """Boolean if shuffle is supported.""" - return bool(self.supported_features & SUPPORT_SHUFFLE_SET) + return bool(self.supported_features & MediaPlayerEntityFeature.SHUFFLE_SET) @property def support_grouping(self): """Boolean if player grouping is supported.""" - return bool(self.supported_features & SUPPORT_GROUPING) + return bool(self.supported_features & MediaPlayerEntityFeature.GROUPING) async def async_toggle(self): """Toggle the power on the media player.""" @@ -854,7 +865,10 @@ class MediaPlayerEntity(Entity): await self.hass.async_add_executor_job(self.volume_up) return - if self.volume_level < 1 and self.supported_features & SUPPORT_VOLUME_SET: + if ( + self.volume_level < 1 + and self.supported_features & MediaPlayerEntityFeature.VOLUME_SET + ): await self.async_set_volume_level(min(1, self.volume_level + 0.1)) async def async_volume_down(self): @@ -866,7 +880,10 @@ class MediaPlayerEntity(Entity): await self.hass.async_add_executor_job(self.volume_down) return - if self.volume_level > 0 and self.supported_features & SUPPORT_VOLUME_SET: + if ( + self.volume_level > 0 + and self.supported_features & MediaPlayerEntityFeature.VOLUME_SET + ): await self.async_set_volume_level(max(0, self.volume_level - 0.1)) async def async_media_play_pause(self): @@ -908,12 +925,12 @@ class MediaPlayerEntity(Entity): supported_features = self.supported_features or 0 data = {} - if supported_features & SUPPORT_SELECT_SOURCE and ( + if supported_features & MediaPlayerEntityFeature.SELECT_SOURCE and ( source_list := self.source_list ): data[ATTR_INPUT_SOURCE_LIST] = source_list - if supported_features & SUPPORT_SELECT_SOUND_MODE and ( + if supported_features & MediaPlayerEntityFeature.SELECT_SOUND_MODE and ( sound_mode_list := self.sound_mode_list ): data[ATTR_SOUND_MODE_LIST] = sound_mode_list @@ -1148,7 +1165,7 @@ async def websocket_browse_media(hass, connection, msg): connection.send_error(msg["id"], "entity_not_found", "Entity not found") return - if not player.supported_features & SUPPORT_BROWSE_MEDIA: + if not player.supported_features & MediaPlayerEntityFeature.BROWSE_MEDIA: connection.send_message( websocket_api.error_message( msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media" diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py index 60234cd1b38..9327bf68f9f 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -20,6 +20,9 @@ from homeassistant.helpers.network import ( from .const import CONTENT_AUTH_EXPIRY_TIME, MEDIA_CLASS_DIRECTORY +# Paths that we don't need to sign +PATHS_WITHOUT_AUTH = ("/api/tts_proxy/",) + @callback def async_process_play_media_url( @@ -46,6 +49,10 @@ def async_process_play_media_url( logging.getLogger(__name__).debug( "Not signing path for content with query param" ) + elif parsed.path.startswith(PATHS_WITHOUT_AUTH): + # We don't sign this path if it doesn't need auth. Although signing itself can't hurt, + # some devices are unable to handle long URLs and the auth signature might push it over. + pass else: signed_path = async_sign_path( hass, diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index c9f0742ea57..b12f0c4ae01 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -118,6 +118,8 @@ class MediaPlayerEntityFeature(IntEnum): GROUPING = 524288 +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the MediaPlayerEntityFeature enum instead. SUPPORT_PAUSE = 1 SUPPORT_SEEK = 2 SUPPORT_VOLUME_SET = 4 diff --git a/homeassistant/components/media_player/device_condition.py b/homeassistant/components/media_player/device_condition.py index 2d8c04dc181..5f57bcea48e 100644 --- a/homeassistant/components/media_player/device_condition.py +++ b/homeassistant/components/media_player/device_condition.py @@ -10,6 +10,7 @@ from homeassistant.const import ( CONF_DOMAIN, CONF_ENTITY_ID, CONF_TYPE, + STATE_BUFFERING, STATE_IDLE, STATE_OFF, STATE_ON, @@ -23,7 +24,14 @@ from homeassistant.helpers.typing import ConfigType, TemplateVarsType from .const import DOMAIN -CONDITION_TYPES = {"is_on", "is_off", "is_idle", "is_paused", "is_playing"} +CONDITION_TYPES = { + "is_on", + "is_off", + "is_buffering", + "is_idle", + "is_paused", + "is_playing", +} CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( { @@ -63,16 +71,18 @@ def async_condition_from_config( hass: HomeAssistant, config: ConfigType ) -> condition.ConditionCheckerType: """Create a function to test a device condition.""" - if config[CONF_TYPE] == "is_playing": - state = STATE_PLAYING + if config[CONF_TYPE] == "is_buffering": + state = STATE_BUFFERING elif config[CONF_TYPE] == "is_idle": state = STATE_IDLE - elif config[CONF_TYPE] == "is_paused": - state = STATE_PAUSED + elif config[CONF_TYPE] == "is_off": + state = STATE_OFF elif config[CONF_TYPE] == "is_on": state = STATE_ON - else: - state = STATE_OFF + elif config[CONF_TYPE] == "is_paused": + state = STATE_PAUSED + else: # is_playing + state = STATE_PLAYING def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool: """Test if an entity is a certain state.""" diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index 1493e28a350..aeed2fd646a 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -21,6 +21,7 @@ from homeassistant.const import ( CONF_FOR, CONF_PLATFORM, CONF_TYPE, + STATE_BUFFERING, STATE_IDLE, STATE_OFF, STATE_ON, @@ -33,7 +34,7 @@ from homeassistant.helpers.typing import ConfigType from .const import DOMAIN -TRIGGER_TYPES = {"turned_on", "turned_off", "idle", "paused", "playing"} +TRIGGER_TYPES = {"turned_on", "turned_off", "buffering", "idle", "paused", "playing"} MEDIA_PLAYER_TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( { @@ -101,15 +102,17 @@ async def async_attach_trigger( """Attach a trigger.""" if config[CONF_TYPE] not in TRIGGER_TYPES: return await entity.async_attach_trigger(hass, config, action, automation_info) - if config[CONF_TYPE] == "turned_on": - to_state = STATE_ON - elif config[CONF_TYPE] == "turned_off": - to_state = STATE_OFF + if config[CONF_TYPE] == "buffering": + to_state = STATE_BUFFERING elif config[CONF_TYPE] == "idle": to_state = STATE_IDLE + elif config[CONF_TYPE] == "turned_off": + to_state = STATE_OFF + elif config[CONF_TYPE] == "turned_on": + to_state = STATE_ON elif config[CONF_TYPE] == "paused": to_state = STATE_PAUSED - else: + else: # "playing" to_state = STATE_PLAYING state_config = { diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index 131366ed95b..586ac61b4e1 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -14,6 +14,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, + STATE_BUFFERING, STATE_IDLE, STATE_OFF, STATE_ON, @@ -71,10 +72,11 @@ async def _async_reproduce_states( if ( state.state in ( - STATE_ON, - STATE_PLAYING, + STATE_BUFFERING, STATE_IDLE, + STATE_ON, STATE_PAUSED, + STATE_PLAYING, ) and features & MediaPlayerEntityFeature.TURN_ON ): @@ -122,7 +124,7 @@ async def _async_reproduce_states( if ( not already_playing - and state.state == STATE_PLAYING + and state.state in (STATE_BUFFERING, STATE_PLAYING) and features & MediaPlayerEntityFeature.PLAY ): await call_service(SERVICE_MEDIA_PLAY, []) diff --git a/homeassistant/components/media_player/strings.json b/homeassistant/components/media_player/strings.json index ea773195380..bb6c7d16f5a 100644 --- a/homeassistant/components/media_player/strings.json +++ b/homeassistant/components/media_player/strings.json @@ -2,6 +2,7 @@ "title": "Media player", "device_automation": { "condition_type": { + "is_buffering": "{entity_name} is buffering", "is_on": "{entity_name} is on", "is_off": "{entity_name} is off", "is_idle": "{entity_name} is idle", @@ -9,6 +10,7 @@ "is_playing": "{entity_name} is playing" }, "trigger_type": { + "buffering": "{entity_name} starts buffering", "turned_on": "{entity_name} turned on", "turned_off": "{entity_name} turned off", "idle": "{entity_name} becomes idle", @@ -24,7 +26,8 @@ "playing": "Playing", "paused": "[%key:common::state::paused%]", "idle": "[%key:common::state::idle%]", - "standby": "[%key:common::state::standby%]" + "standby": "[%key:common::state::standby%]", + "buffering": "Buffering" } } } diff --git a/homeassistant/components/media_player/translations/de.json b/homeassistant/components/media_player/translations/de.json index 653533ba03a..d6468920628 100644 --- a/homeassistant/components/media_player/translations/de.json +++ b/homeassistant/components/media_player/translations/de.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} puffert", "is_idle": "{entity_name} ist unt\u00e4tig", "is_off": "{entity_name} ist ausgeschaltet", "is_on": "{entity_name} ist eingeschaltet", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} spielt" }, "trigger_type": { + "buffering": "{entity_name} beginnt mit dem Puffern", "changed_states": "{entity_name} hat den Status ge\u00e4ndert", "idle": "{entity_name} wird inaktiv", "paused": "{entity_name} ist angehalten", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Puffern", "idle": "Unt\u00e4tig", "off": "Aus", "on": "An", diff --git a/homeassistant/components/media_player/translations/el.json b/homeassistant/components/media_player/translations/el.json index 242de3e829a..d7819069e26 100644 --- a/homeassistant/components/media_player/translations/el.json +++ b/homeassistant/components/media_player/translations/el.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} \u03be\u03b5\u03ba\u03b9\u03bd\u03ac\u03b5\u03b9 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7", "is_idle": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03b4\u03c1\u03b1\u03bd\u03ad\u03c2", "is_off": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", "is_on": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} \u03c0\u03b1\u03af\u03b6\u03b5\u03b9" }, "trigger_type": { + "buffering": "{entity_name} \u03be\u03b5\u03ba\u03b9\u03bd\u03ac\u03b5\u03b9 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7", "changed_states": "\u03a4\u03bf {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2", "idle": "{entity_name} \u03b3\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03b4\u03c1\u03b1\u03bd\u03ad\u03c2", "paused": "{entity_name} \u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03b5 \u03c0\u03b1\u03cd\u03c3\u03b7", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "\u03a0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ae \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7", "idle": "\u03a3\u03b5 \u03b1\u03b4\u03c1\u03ac\u03bd\u03b5\u03b9\u03b1", "off": "\u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc", "on": "\u0395\u03bd\u03b5\u03c1\u03b3\u03cc", diff --git a/homeassistant/components/media_player/translations/en.json b/homeassistant/components/media_player/translations/en.json index 78b37745a2b..119c057167e 100644 --- a/homeassistant/components/media_player/translations/en.json +++ b/homeassistant/components/media_player/translations/en.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} is buffering", "is_idle": "{entity_name} is idle", "is_off": "{entity_name} is off", "is_on": "{entity_name} is on", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} is playing" }, "trigger_type": { + "buffering": "{entity_name} starts buffering", "changed_states": "{entity_name} changed states", "idle": "{entity_name} becomes idle", "paused": "{entity_name} is paused", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Buffering", "idle": "Idle", "off": "Off", "on": "On", diff --git a/homeassistant/components/media_player/translations/et.json b/homeassistant/components/media_player/translations/et.json index 5461600c9d5..ae3293e951f 100644 --- a/homeassistant/components/media_player/translations/et.json +++ b/homeassistant/components/media_player/translations/et.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} puhverdab", "is_idle": "{entity_name} on j\u00f5udeolekus", "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", "is_on": "{entity_name} on sisse l\u00fclitatud", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} m\u00e4ngib" }, "trigger_type": { + "buffering": "{entity_name} alustab puhverdamist", "changed_states": "{entity_name} muutis olekut", "idle": "{entity_name} muutub j\u00f5udeolekusse", "paused": "{entity_name} on pausil", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Puhverdamine", "idle": "Ootel", "off": "V\u00e4ljas", "on": "Sees", diff --git a/homeassistant/components/media_player/translations/fr.json b/homeassistant/components/media_player/translations/fr.json index 17a6cbf92b3..d6c41bf5e09 100644 --- a/homeassistant/components/media_player/translations/fr.json +++ b/homeassistant/components/media_player/translations/fr.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} est en train de mettre en m\u00e9moire tampon", "is_idle": "{entity_name} est inactif", "is_off": "{entity_name} est d\u00e9sactiv\u00e9", "is_on": "{entity_name} est activ\u00e9", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} joue" }, "trigger_type": { + "buffering": "{entity_name} commence \u00e0 mettre en m\u00e9moire tampon", "changed_states": "{entity_name} a chang\u00e9 d'\u00e9tat", "idle": "{entity_name} devient inactif", "paused": "{entity_name} est mis en pause", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Mise en m\u00e9moire tampon", "idle": "Inactif", "off": "D\u00e9sactiv\u00e9", "on": "Activ\u00e9", diff --git a/homeassistant/components/media_player/translations/hu.json b/homeassistant/components/media_player/translations/hu.json index 83b5dc4e122..3a885c70b64 100644 --- a/homeassistant/components/media_player/translations/hu.json +++ b/homeassistant/components/media_player/translations/hu.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} pufferel", "is_idle": "{entity_name} t\u00e9tlen", "is_off": "{entity_name} ki van kapcsolva", "is_on": "{entity_name} be van kapcsolva", @@ -8,6 +9,8 @@ "is_playing": "{entity_name} lej\u00e1tszik" }, "trigger_type": { + "buffering": "{entity_name} pufferelni kezd", + "changed_states": "{entity_name} \u00e1llapota megv\u00e1ltozott", "idle": "{entity_name} t\u00e9tlenn\u00e9 v\u00e1lik", "paused": "{entity_name} sz\u00fcneteltetve van", "playing": "{entity_name} megkezdi a lej\u00e1tsz\u00e1st", @@ -17,6 +20,7 @@ }, "state": { "_": { + "buffering": "Pufferel\u00e9s", "idle": "T\u00e9tlen", "off": "Ki", "on": "Be", diff --git a/homeassistant/components/media_player/translations/id.json b/homeassistant/components/media_player/translations/id.json index 9446d1e9e89..469687f9110 100644 --- a/homeassistant/components/media_player/translations/id.json +++ b/homeassistant/components/media_player/translations/id.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} sedang buffering", "is_idle": "{entity_name} siaga", "is_off": "{entity_name} mati", "is_on": "{entity_name} nyala", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} sedang memutar" }, "trigger_type": { + "buffering": "{entity_name} mulai buffering", "changed_states": "{entity_name} mengubah status", "idle": "{entity_name} menjadi siaga", "paused": "{entity_name} dijeda", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Buffering", "idle": "Siaga", "off": "Mati", "on": "Nyala", diff --git a/homeassistant/components/media_player/translations/it.json b/homeassistant/components/media_player/translations/it.json index 7c075420aba..5fe897a9a3f 100644 --- a/homeassistant/components/media_player/translations/it.json +++ b/homeassistant/components/media_player/translations/it.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} \u00e8 in buffering", "is_idle": "{entity_name} \u00e8 inattivo", "is_off": "{entity_name} \u00e8 spento", "is_on": "{entity_name} \u00e8 acceso", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} \u00e8 in esecuzione" }, "trigger_type": { + "buffering": "{entity_name} avvia il buffering", "changed_states": "{entity_name} ha cambiato stato", "idle": "{entity_name} diventa inattivo", "paused": "{entity_name} \u00e8 in pausa", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Riempimento buffer", "idle": "Inattivo", "off": "Spento", "on": "Acceso", diff --git a/homeassistant/components/media_player/translations/nl.json b/homeassistant/components/media_player/translations/nl.json index 5fae215f4f9..23fe64d452a 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} is aan het bufferen", "is_idle": "{entity_name} is niet actief", "is_off": "{entity_name} is uitgeschakeld", "is_on": "{entity_name} is ingeschakeld", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} wordt afgespeeld" }, "trigger_type": { + "buffering": "{entity_name} start met bufferen", "changed_states": "{entity_name} veranderde van status", "idle": "{entity_name} wordt inactief", "paused": "{entity_name} is gepauzeerd", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Bufferen", "idle": "Inactief", "off": "Uit", "on": "Aan", diff --git a/homeassistant/components/media_player/translations/no.json b/homeassistant/components/media_player/translations/no.json index c51920a67bd..d7dd387b4e3 100644 --- a/homeassistant/components/media_player/translations/no.json +++ b/homeassistant/components/media_player/translations/no.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} bufre", "is_idle": "{entity_name} er inaktiv", "is_off": "{entity_name} er sl\u00e5tt av", "is_on": "{entity_name} er sl\u00e5tt p\u00e5", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} spiller n\u00e5" }, "trigger_type": { + "buffering": "{entity_name} starter bufring", "changed_states": "{entity_name} endret tilstander", "idle": "{entity_name} blir inaktiv", "paused": "{entity_name} er satt p\u00e5 pause", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Bufring", "idle": "Inaktiv", "off": "Av", "on": "P\u00e5", diff --git a/homeassistant/components/media_player/translations/pl.json b/homeassistant/components/media_player/translations/pl.json index 08c664a909d..886cb07f93f 100644 --- a/homeassistant/components/media_player/translations/pl.json +++ b/homeassistant/components/media_player/translations/pl.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} buforuje", "is_idle": "odtwarzacz {entity_name} jest nieaktywny", "is_off": "odtwarzacz {entity_name} jest wy\u0142\u0105czony", "is_on": "odtwarzacz {entity_name} jest w\u0142\u0105czony", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} odtwarza media" }, "trigger_type": { + "buffering": "{entity_name} rozpocznie buforowanie", "changed_states": "{entity_name} zmieni\u0142o stan", "idle": "odtwarzacz {entity_name} stanie si\u0119 bezczynny", "paused": "odtwarzacz {entity_name} zostanie wstrzymany", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Buforowanie", "idle": "nieaktywny", "off": "wy\u0142.", "on": "w\u0142.", diff --git a/homeassistant/components/media_player/translations/pt-BR.json b/homeassistant/components/media_player/translations/pt-BR.json index 147f66ec0e7..332d4d0bcb4 100644 --- a/homeassistant/components/media_player/translations/pt-BR.json +++ b/homeassistant/components/media_player/translations/pt-BR.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} est\u00e1 em buffer", "is_idle": "{entity_name} est\u00e1 ocioso", "is_off": "{entity_name} est\u00e1 desligado", "is_on": "{entity_name} est\u00e1 ligado", @@ -8,6 +9,7 @@ "is_playing": "{entity_name} est\u00e1 reproduzindo" }, "trigger_type": { + "buffering": "{entity_name} inicia o armazenamento em buffer", "changed_states": "{entity_name} ligado ou desligado", "idle": "{entity_name} ficar ocioso", "paused": "{entity_name} for pausado", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "Carregando", "idle": "Ocioso", "off": "Desligado", "on": "Ligado", diff --git a/homeassistant/components/media_player/translations/zh-Hant.json b/homeassistant/components/media_player/translations/zh-Hant.json index b4e8442ea8d..99e55cc328c 100644 --- a/homeassistant/components/media_player/translations/zh-Hant.json +++ b/homeassistant/components/media_player/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "device_automation": { "condition_type": { + "is_buffering": "{entity_name} \u7de9\u885d\u4e2d", "is_idle": "{entity_name}\u9592\u7f6e", "is_off": "{entity_name}\u95dc\u9589", "is_on": "{entity_name}\u958b\u555f", @@ -8,6 +9,7 @@ "is_playing": "{entity_name}\u6b63\u5728\u64ad\u653e" }, "trigger_type": { + "buffering": "{entity_name} \u958b\u59cb\u7de9\u885d", "changed_states": "{entity_name}\u5df2\u8b8a\u66f4\u72c0\u614b", "idle": "{entity_name}\u8b8a\u6210\u9592\u7f6e", "paused": "{entity_name}\u5df2\u66ab\u505c", @@ -18,6 +20,7 @@ }, "state": { "_": { + "buffering": "\u7de9\u885d", "idle": "\u9592\u7f6e", "off": "\u95dc\u9589", "on": "\u958b\u555f", diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 66baa0eaa8c..89feba5317f 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -180,9 +180,10 @@ class LocalSource(MediaSource): # Append first level children media.children = [] for child_path in path.iterdir(): - child = self._build_item_response(source_dir_id, child_path, True) - if child: - media.children.append(child) + if child_path.name[0] != ".": + child = self._build_item_response(source_dir_id, child_path, True) + if child: + media.children.append(child) # Sort children showing directories first, then by name media.children.sort(key=lambda child: (child.can_play, child.title)) @@ -300,9 +301,7 @@ class UploadMediaView(http.HomeAssistantView): {"media_content_id": f"{data['media_content_id']}/{uploaded_file.filename}"} ) - def _move_file( # pylint: disable=no-self-use - self, target_dir: Path, uploaded_file: FileField - ) -> None: + def _move_file(self, target_dir: Path, uploaded_file: FileField) -> None: """Move file to target.""" if not target_dir.is_dir(): raise ValueError("Target is not an existing directory") diff --git a/homeassistant/components/mediaroom/media_player.py b/homeassistant/components/mediaroom/media_player.py index bb0f0da39bc..31c8d634912 100644 --- a/homeassistant/components/mediaroom/media_player.py +++ b/homeassistant/components/mediaroom/media_player.py @@ -12,20 +12,12 @@ from pymediaroom import ( ) import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_CHANNEL from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -54,18 +46,7 @@ DISCOVERY_MEDIAROOM = "mediaroom_discovery_installed" MEDIA_TYPE_MEDIAROOM = "mediaroom" SIGNAL_STB_NOTIFY = "mediaroom_stb_discovered" -SUPPORT_MEDIAROOM = ( - SUPPORT_PAUSE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_PLAY -) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -136,6 +117,19 @@ async def async_setup_platform( class MediaroomDevice(MediaPlayerEntity): """Representation of a Mediaroom set-up-box on the network.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.PLAY + ) + def set_state(self, mediaroom_state): """Map pymediaroom state to HA state.""" @@ -242,11 +236,6 @@ class MediaroomDevice(MediaPlayerEntity): """Return the state of the device.""" return self._state - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_MEDIAROOM - @property def media_content_type(self): """Return the content type of current playing media.""" diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index 4e8c56b3a67..b16221923de 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -19,15 +19,8 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -52,18 +45,18 @@ SCAN_INTERVAL = timedelta(seconds=60) ATA_HVAC_MODE_LOOKUP = { - ata.OPERATION_MODE_HEAT: HVAC_MODE_HEAT, - ata.OPERATION_MODE_DRY: HVAC_MODE_DRY, - ata.OPERATION_MODE_COOL: HVAC_MODE_COOL, - ata.OPERATION_MODE_FAN_ONLY: HVAC_MODE_FAN_ONLY, - ata.OPERATION_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, + ata.OPERATION_MODE_HEAT: HVACMode.HEAT, + ata.OPERATION_MODE_DRY: HVACMode.DRY, + ata.OPERATION_MODE_COOL: HVACMode.COOL, + ata.OPERATION_MODE_FAN_ONLY: HVACMode.FAN_ONLY, + ata.OPERATION_MODE_HEAT_COOL: HVACMode.HEAT_COOL, } ATA_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATA_HVAC_MODE_LOOKUP.items()} ATW_ZONE_HVAC_MODE_LOOKUP = { - atw.ZONE_OPERATION_MODE_HEAT: HVAC_MODE_HEAT, - atw.ZONE_OPERATION_MODE_COOL: HVAC_MODE_COOL, + atw.ZONE_OPERATION_MODE_HEAT: HVACMode.HEAT, + atw.ZONE_OPERATION_MODE_COOL: HVACMode.COOL, } ATW_ZONE_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATW_ZONE_HVAC_MODE_LOOKUP.items()} @@ -128,7 +121,9 @@ class AtaDeviceClimate(MelCloudClimate): """Air-to-Air climate device.""" _attr_supported_features = ( - SUPPORT_FAN_MODE | SUPPORT_TARGET_TEMPERATURE | SUPPORT_SWING_MODE + ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.SWING_MODE ) def __init__(self, device: MelCloudDevice, ata_device: AtaDevice) -> None: @@ -162,16 +157,16 @@ class AtaDeviceClimate(MelCloudClimate): return attr @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" mode = self._device.operation_mode if not self._device.power or mode is None: - return HVAC_MODE_OFF + return HVACMode.OFF return ATA_HVAC_MODE_LOOKUP.get(mode) def _apply_set_hvac_mode(self, hvac_mode: str, set_dict: dict[str, Any]) -> None: """Apply hvac mode changes to a dict used to call _device.set.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: set_dict["power"] = False return @@ -180,19 +175,19 @@ class AtaDeviceClimate(MelCloudClimate): raise ValueError(f"Invalid hvac_mode [{hvac_mode}]") set_dict["operation_mode"] = operation_mode - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: set_dict["power"] = True - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" set_dict = {} self._apply_set_hvac_mode(hvac_mode, set_dict) await self._device.set(set_dict) @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" - return [HVAC_MODE_OFF] + [ + return [HVACMode.OFF] + [ ATA_HVAC_MODE_LOOKUP.get(mode) for mode in self._device.operation_modes ] @@ -296,7 +291,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): _attr_max_temp = 30 _attr_min_temp = 10 - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE def __init__( self, device: MelCloudDevice, atw_device: AtwDevice, atw_zone: Zone @@ -320,16 +315,16 @@ class AtwDeviceZoneClimate(MelCloudClimate): return data @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" mode = self._zone.operation_mode if not self._device.power or mode is None: - return HVAC_MODE_OFF - return ATW_ZONE_HVAC_MODE_LOOKUP.get(mode, HVAC_MODE_OFF) + return HVACMode.OFF + return ATW_ZONE_HVAC_MODE_LOOKUP.get(mode, HVACMode.OFF) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._device.set({"power": False}) return @@ -341,12 +336,12 @@ class AtwDeviceZoneClimate(MelCloudClimate): props = {PROPERTY_ZONE_1_OPERATION_MODE: operation_mode} else: props = {PROPERTY_ZONE_2_OPERATION_MODE: operation_mode} - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: props["power"] = True await self._device.set(props) @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" return [self.hvac_mode] diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index 84bc5d36110..c9075abb008 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -9,9 +9,8 @@ from pymelcloud.atw_device import ( from pymelcloud.device import PROPERTY_POWER from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS @@ -39,6 +38,11 @@ async def async_setup_entry( class AtwWaterHeater(WaterHeaterEntity): """Air-to-Water water heater.""" + _attr_supported_features = ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + ) + def __init__(self, api: MelCloudDevice, device: AtwDevice) -> None: """Initialize water heater device.""" self._api = api @@ -117,11 +121,6 @@ class AtwWaterHeater(WaterHeaterEntity): """Set new target operation mode.""" await self._device.set({PROPERTY_OPERATION_MODE: operation_mode}) - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE - @property def min_temp(self) -> float | None: """Return the minimum temperature.""" diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index cd4468360b3..81ffd9a5d1e 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -9,14 +9,8 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -27,14 +21,12 @@ from . import DATA_MELISSA _LOGGER = logging.getLogger(__name__) -SUPPORT_FLAGS = SUPPORT_FAN_MODE | SUPPORT_TARGET_TEMPERATURE - OP_MODES = [ - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_OFF, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.DRY, + HVACMode.FAN_ONLY, + HVACMode.OFF, ] FAN_MODES = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW] @@ -62,6 +54,11 @@ async def async_setup_platform( class MelissaClimate(ClimateEntity): """Representation of a Melissa Climate device.""" + _attr_hvac_modes = OP_MODES + _attr_supported_features = ( + ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) + def __init__(self, api, serial_number, init_data): """Initialize the climate device.""" self._name = init_data["name"] @@ -100,7 +97,7 @@ class MelissaClimate(ClimateEntity): return PRECISION_WHOLE @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode | None: """Return the current operation mode.""" if self._cur_settings is None: return None @@ -111,15 +108,10 @@ class MelissaClimate(ClimateEntity): ) if not is_on: - return HVAC_MODE_OFF + return HVACMode.OFF return self.melissa_op_to_hass(self._cur_settings[self._api.MODE]) - @property - def hvac_modes(self): - """Return the list of available operation modes.""" - return OP_MODES - @property def fan_modes(self): """List of available fan modes.""" @@ -147,11 +139,6 @@ class MelissaClimate(ClimateEntity): """Return the maximum supported temperature for the thermostat.""" return 30 - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) @@ -162,9 +149,9 @@ class MelissaClimate(ClimateEntity): melissa_fan_mode = self.hass_fan_to_melissa(fan_mode) await self.async_send({self._api.FAN: melissa_fan_mode}) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_send({self._api.STATE: self._api.STATE_OFF}) return @@ -200,20 +187,20 @@ class MelissaClimate(ClimateEntity): def melissa_op_to_hass(self, mode): """Translate Melissa modes to hass states.""" if mode == self._api.MODE_HEAT: - return HVAC_MODE_HEAT + return HVACMode.HEAT if mode == self._api.MODE_COOL: - return HVAC_MODE_COOL + return HVACMode.COOL if mode == self._api.MODE_DRY: - return HVAC_MODE_DRY + return HVACMode.DRY if mode == self._api.MODE_FAN: - return HVAC_MODE_FAN_ONLY + return HVACMode.FAN_ONLY _LOGGER.warning("Operation mode %s could not be mapped to hass", mode) return None def melissa_fan_to_hass(self, fan): """Translate Melissa fan modes to hass modes.""" if fan == self._api.FAN_AUTO: - return HVAC_MODE_AUTO + return FAN_AUTO if fan == self._api.FAN_LOW: return FAN_LOW if fan == self._api.FAN_MEDIUM: @@ -225,19 +212,19 @@ class MelissaClimate(ClimateEntity): def hass_mode_to_melissa(self, mode): """Translate hass states to melissa modes.""" - if mode == HVAC_MODE_HEAT: + if mode == HVACMode.HEAT: return self._api.MODE_HEAT - if mode == HVAC_MODE_COOL: + if mode == HVACMode.COOL: return self._api.MODE_COOL - if mode == HVAC_MODE_DRY: + if mode == HVACMode.DRY: return self._api.MODE_DRY - if mode == HVAC_MODE_FAN_ONLY: + if mode == HVACMode.FAN_ONLY: return self._api.MODE_FAN _LOGGER.warning("Melissa have no setting for %s mode", mode) def hass_fan_to_melissa(self, fan): """Translate hass fan modes to melissa modes.""" - if fan == HVAC_MODE_AUTO: + if fan == FAN_AUTO: return self._api.FAN_AUTO if fan == FAN_LOW: return self._api.FAN_LOW diff --git a/homeassistant/components/met/translations/hu.json b/homeassistant/components/met/translations/hu.json index 38c84a3f8dc..ffa19641cd1 100644 --- a/homeassistant/components/met/translations/hu.json +++ b/homeassistant/components/met/translations/hu.json @@ -12,7 +12,7 @@ "elevation": "Magass\u00e1g", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Meteorol\u00f3giai int\u00e9zet", "title": "Elhelyezked\u00e9s" diff --git a/homeassistant/components/met_eireann/translations/hu.json b/homeassistant/components/met_eireann/translations/hu.json index b70aa2dcf67..8ce232f26a9 100644 --- a/homeassistant/components/met_eireann/translations/hu.json +++ b/homeassistant/components/met_eireann/translations/hu.json @@ -9,7 +9,7 @@ "elevation": "Magass\u00e1g", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Adja meg tart\u00f3zkod\u00e1si hely\u00e9t a Met \u00c9ireann Public Weather Forecast API id\u0151j\u00e1r\u00e1si adatainak haszn\u00e1lat\u00e1hoz", "title": "Elhelyezked\u00e9s" diff --git a/homeassistant/components/meteo_france/translations/bg.json b/homeassistant/components/meteo_france/translations/bg.json index c426ae62387..d4a3ab09c9d 100644 --- a/homeassistant/components/meteo_france/translations/bg.json +++ b/homeassistant/components/meteo_france/translations/bg.json @@ -9,14 +9,12 @@ "data": { "city": "\u0413\u0440\u0430\u0434" }, - "description": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0432\u0430\u0448\u0438\u044f \u0433\u0440\u0430\u0434 \u043e\u0442 \u0441\u043f\u0438\u0441\u044a\u043a\u0430", - "title": "M\u00e9t\u00e9o-France" + "description": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0432\u0430\u0448\u0438\u044f \u0433\u0440\u0430\u0434 \u043e\u0442 \u0441\u043f\u0438\u0441\u044a\u043a\u0430" }, "user": { "data": { "city": "\u0413\u0440\u0430\u0434" - }, - "title": "M\u00e9t\u00e9o-France" + } } } } diff --git a/homeassistant/components/meteo_france/translations/ca.json b/homeassistant/components/meteo_france/translations/ca.json index 3fb28025f99..af3ee56159c 100644 --- a/homeassistant/components/meteo_france/translations/ca.json +++ b/homeassistant/components/meteo_france/translations/ca.json @@ -12,15 +12,13 @@ "data": { "city": "Ciutat" }, - "description": "Tria una ciutat de la llista", - "title": "M\u00e9t\u00e9o-France" + "description": "Tria una ciutat de la llista" }, "user": { "data": { "city": "Ciutat" }, - "description": "Introdueix el codi postal (nom\u00e9s recomanat per Fran\u00e7a) o nom de la ciutat", - "title": "M\u00e9t\u00e9o-France" + "description": "Introdueix el codi postal (nom\u00e9s recomanat per Fran\u00e7a) o nom de la ciutat" } } }, @@ -28,7 +26,7 @@ "step": { "init": { "data": { - "mode": "Mode de predicci\u00f3" + "mode": "Mode previsi\u00f3" } } } diff --git a/homeassistant/components/meteo_france/translations/cs.json b/homeassistant/components/meteo_france/translations/cs.json index d1c8f632220..0f05ce14ffc 100644 --- a/homeassistant/components/meteo_france/translations/cs.json +++ b/homeassistant/components/meteo_france/translations/cs.json @@ -12,15 +12,13 @@ "data": { "city": "M\u011bsto" }, - "description": "Vyberte m\u011bsto ze seznamu", - "title": "M\u00e9t\u00e9o-France" + "description": "Vyberte m\u011bsto ze seznamu" }, "user": { "data": { "city": "M\u011bsto" }, - "description": "Zadejte PS\u010c (pouze pro Francii, doporu\u010deno) nebo jm\u00e9no m\u011bsta.", - "title": "M\u00e9t\u00e9o-France" + "description": "Zadejte PS\u010c (pouze pro Francii, doporu\u010deno) nebo jm\u00e9no m\u011bsta." } } }, diff --git a/homeassistant/components/meteo_france/translations/da.json b/homeassistant/components/meteo_france/translations/da.json index d0fe7cba892..35c26b38083 100644 --- a/homeassistant/components/meteo_france/translations/da.json +++ b/homeassistant/components/meteo_france/translations/da.json @@ -9,8 +9,7 @@ "data": { "city": "By" }, - "description": "Indtast postnummer (kun for Frankrig, anbefalet) eller bynavn", - "title": "M\u00e9t\u00e9o-France" + "description": "Indtast postnummer (kun for Frankrig, anbefalet) eller bynavn" } } } diff --git a/homeassistant/components/meteo_france/translations/de.json b/homeassistant/components/meteo_france/translations/de.json index 04d038cb65d..ae0d7ab2f14 100644 --- a/homeassistant/components/meteo_france/translations/de.json +++ b/homeassistant/components/meteo_france/translations/de.json @@ -12,15 +12,13 @@ "data": { "city": "Stadt" }, - "description": "W\u00e4hle deine Stadt aus der Liste", - "title": "M\u00e9t\u00e9o-France" + "description": "W\u00e4hle deine Stadt aus der Liste" }, "user": { "data": { "city": "Stadt" }, - "description": "Gib die Postleitzahl (nur f\u00fcr Frankreich empfohlen) oder den St\u00e4dtenamen ein", - "title": "M\u00e9t\u00e9o-France" + "description": "Gib die Postleitzahl (nur f\u00fcr Frankreich empfohlen) oder den St\u00e4dtenamen ein" } } }, diff --git a/homeassistant/components/meteo_france/translations/el.json b/homeassistant/components/meteo_france/translations/el.json index 3dfad5a493d..038279abece 100644 --- a/homeassistant/components/meteo_france/translations/el.json +++ b/homeassistant/components/meteo_france/translations/el.json @@ -12,15 +12,13 @@ "data": { "city": "\u03a0\u03cc\u03bb\u03b7" }, - "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cc\u03bb\u03b7 \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1", - "title": "M\u00e9t\u00e9o-France" + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03cc\u03bb\u03b7 \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1" }, "user": { "data": { "city": "\u03a0\u03cc\u03bb\u03b7" }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 (\u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b7 \u0393\u03b1\u03bb\u03bb\u03af\u03b1, \u03c3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9) \u03ae \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cc\u03bb\u03b7\u03c2.", - "title": "M\u00e9t\u00e9o-France" + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 (\u03bc\u03cc\u03bd\u03bf \u03b3\u03b9\u03b1 \u03c4\u03b7 \u0393\u03b1\u03bb\u03bb\u03af\u03b1, \u03c3\u03c5\u03bd\u03b9\u03c3\u03c4\u03ac\u03c4\u03b1\u03b9) \u03ae \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c0\u03cc\u03bb\u03b7\u03c2." } } }, diff --git a/homeassistant/components/meteo_france/translations/en.json b/homeassistant/components/meteo_france/translations/en.json index b1c4a7c0216..96302d7105b 100644 --- a/homeassistant/components/meteo_france/translations/en.json +++ b/homeassistant/components/meteo_france/translations/en.json @@ -12,15 +12,13 @@ "data": { "city": "City" }, - "description": "Choose your city from the list", - "title": "M\u00e9t\u00e9o-France" + "description": "Choose your city from the list" }, "user": { "data": { "city": "City" }, - "description": "Enter the postal code (only for France, recommended) or city name", - "title": "M\u00e9t\u00e9o-France" + "description": "Enter the postal code (only for France, recommended) or city name" } } }, diff --git a/homeassistant/components/meteo_france/translations/es-419.json b/homeassistant/components/meteo_france/translations/es-419.json index 471b965a824..e2c2feb5ed0 100644 --- a/homeassistant/components/meteo_france/translations/es-419.json +++ b/homeassistant/components/meteo_france/translations/es-419.json @@ -9,8 +9,7 @@ "data": { "city": "Ciudad" }, - "description": "Ingrese el c\u00f3digo postal (solo para Francia, recomendado) o el nombre de la ciudad", - "title": "M\u00e9t\u00e9o-Francia" + "description": "Ingrese el c\u00f3digo postal (solo para Francia, recomendado) o el nombre de la ciudad" } } } diff --git a/homeassistant/components/meteo_france/translations/es.json b/homeassistant/components/meteo_france/translations/es.json index 31d221eba09..8843d301779 100644 --- a/homeassistant/components/meteo_france/translations/es.json +++ b/homeassistant/components/meteo_france/translations/es.json @@ -12,15 +12,13 @@ "data": { "city": "Ciudad" }, - "description": "Elige tu ciudad de la lista", - "title": "M\u00e9t\u00e9o-France" + "description": "Elige tu ciudad de la lista" }, "user": { "data": { "city": "Ciudad" }, - "description": "Introduzca el c\u00f3digo postal (solo para Francia, recomendado) o el nombre de la ciudad", - "title": "M\u00e9t\u00e9o-France" + "description": "Introduzca el c\u00f3digo postal (solo para Francia, recomendado) o el nombre de la ciudad" } } }, diff --git a/homeassistant/components/meteo_france/translations/et.json b/homeassistant/components/meteo_france/translations/et.json index c23d2107f54..98c739fbcff 100644 --- a/homeassistant/components/meteo_france/translations/et.json +++ b/homeassistant/components/meteo_france/translations/et.json @@ -12,15 +12,13 @@ "data": { "city": "Linn" }, - "description": "Vali loendist oma linn", - "title": "" + "description": "Vali loendist oma linn" }, "user": { "data": { "city": "Linn" }, - "description": "Sisesta sihtnumber (ainult Prantsusmaa, soovitatav) v\u00f5i linna nimi", - "title": "" + "description": "Sisesta sihtnumber (ainult Prantsusmaa, soovitatav) v\u00f5i linna nimi" } } }, diff --git a/homeassistant/components/meteo_france/translations/fr.json b/homeassistant/components/meteo_france/translations/fr.json index 1121dd02b17..a2c543ee1fa 100644 --- a/homeassistant/components/meteo_france/translations/fr.json +++ b/homeassistant/components/meteo_france/translations/fr.json @@ -12,15 +12,13 @@ "data": { "city": "Ville" }, - "description": "Choisissez votre ville dans la liste", - "title": "M\u00e9t\u00e9o-France" + "description": "Choisissez votre ville dans la liste" }, "user": { "data": { "city": "Ville" }, - "description": "Entrez le code postal (uniquement pour la France, recommand\u00e9) ou le nom de la ville", - "title": "M\u00e9t\u00e9o-France" + "description": "Entrez le code postal (uniquement pour la France, recommand\u00e9) ou le nom de la ville" } } }, diff --git a/homeassistant/components/meteo_france/translations/hu.json b/homeassistant/components/meteo_france/translations/hu.json index 8034f6d0586..b9c2485a30b 100644 --- a/homeassistant/components/meteo_france/translations/hu.json +++ b/homeassistant/components/meteo_france/translations/hu.json @@ -12,15 +12,13 @@ "data": { "city": "V\u00e1ros" }, - "description": "V\u00e1lassza ki a v\u00e1rost a list\u00e1b\u00f3l", - "title": "M\u00e9t\u00e9o-France" + "description": "V\u00e1lassza ki a v\u00e1rost a list\u00e1b\u00f3l" }, "user": { "data": { "city": "V\u00e1ros" }, - "description": "\u00cdrja be az ir\u00e1ny\u00edt\u00f3sz\u00e1mot (csak Franciaorsz\u00e1g eset\u00e9ben aj\u00e1nlott) vagy a v\u00e1ros nev\u00e9t", - "title": "M\u00e9t\u00e9o-France" + "description": "\u00cdrja be az ir\u00e1ny\u00edt\u00f3sz\u00e1mot (csak Franciaorsz\u00e1g eset\u00e9ben aj\u00e1nlott) vagy a v\u00e1ros nev\u00e9t" } } }, diff --git a/homeassistant/components/meteo_france/translations/id.json b/homeassistant/components/meteo_france/translations/id.json index 07d8450e873..5c7d90710b5 100644 --- a/homeassistant/components/meteo_france/translations/id.json +++ b/homeassistant/components/meteo_france/translations/id.json @@ -12,15 +12,13 @@ "data": { "city": "Kota" }, - "description": "Pilih kota Anda dari daftar", - "title": "M\u00e9t\u00e9o-France" + "description": "Pilih kota Anda dari daftar" }, "user": { "data": { "city": "Kota" }, - "description": "Masukkan kode pos (hanya untuk Prancis, disarankan) atau nama kota", - "title": "M\u00e9t\u00e9o-France" + "description": "Masukkan kode pos (hanya untuk Prancis, disarankan) atau nama kota" } } }, diff --git a/homeassistant/components/meteo_france/translations/it.json b/homeassistant/components/meteo_france/translations/it.json index 37588fb4a32..78bad9d7dfd 100644 --- a/homeassistant/components/meteo_france/translations/it.json +++ b/homeassistant/components/meteo_france/translations/it.json @@ -12,15 +12,13 @@ "data": { "city": "Citt\u00e0" }, - "description": "Scegli la tua citt\u00e0 dall'elenco", - "title": "M\u00e9t\u00e9o-France" + "description": "Scegli la tua citt\u00e0 dall'elenco" }, "user": { "data": { "city": "Citt\u00e0" }, - "description": "Inserisci il codice postale (solo per la Francia, consigliato) o il nome della citt\u00e0", - "title": "M\u00e9t\u00e9o-France" + "description": "Inserisci il codice postale (solo per la Francia, consigliato) o il nome della citt\u00e0" } } }, diff --git a/homeassistant/components/meteo_france/translations/ja.json b/homeassistant/components/meteo_france/translations/ja.json index 2fa1f60225e..8e6b37cd53d 100644 --- a/homeassistant/components/meteo_france/translations/ja.json +++ b/homeassistant/components/meteo_france/translations/ja.json @@ -12,15 +12,13 @@ "data": { "city": "\u90fd\u5e02" }, - "description": "\u30ea\u30b9\u30c8\u304b\u3089\u3042\u306a\u305f\u306e\u90fd\u5e02\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "M\u00e9t\u00e9o-France" + "description": "\u30ea\u30b9\u30c8\u304b\u3089\u3042\u306a\u305f\u306e\u90fd\u5e02\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044" }, "user": { "data": { "city": "\u90fd\u5e02" }, - "description": "\u90f5\u4fbf\u756a\u53f7(\u30d5\u30e9\u30f3\u30b9\u306e\u307f\u3001\u63a8\u5968) \u307e\u305f\u306f\u3001\u5e02\u533a\u753a\u6751\u540d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044", - "title": "M\u00e9t\u00e9o-France" + "description": "\u90f5\u4fbf\u756a\u53f7(\u30d5\u30e9\u30f3\u30b9\u306e\u307f\u3001\u63a8\u5968) \u307e\u305f\u306f\u3001\u5e02\u533a\u753a\u6751\u540d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044" } } }, diff --git a/homeassistant/components/meteo_france/translations/ko.json b/homeassistant/components/meteo_france/translations/ko.json index 83cda0e4dcf..f977d7d6915 100644 --- a/homeassistant/components/meteo_france/translations/ko.json +++ b/homeassistant/components/meteo_france/translations/ko.json @@ -12,15 +12,13 @@ "data": { "city": "\ub3c4\uc2dc" }, - "description": "\ubaa9\ub85d\uc5d0\uc11c \ub3c4\uc2dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "title": "\ud504\ub791\uc2a4 \uae30\uc0c1\uccad (M\u00e9t\u00e9o-France)" + "description": "\ubaa9\ub85d\uc5d0\uc11c \ub3c4\uc2dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, "user": { "data": { "city": "\ub3c4\uc2dc" }, - "description": "\uc6b0\ud3b8\ubc88\ud638 (\ud504\ub791\uc2a4) \ub610\ub294 \ub3c4\uc2dc \uc774\ub984\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "\ud504\ub791\uc2a4 \uae30\uc0c1\uccad (M\u00e9t\u00e9o-France)" + "description": "\uc6b0\ud3b8\ubc88\ud638 (\ud504\ub791\uc2a4) \ub610\ub294 \ub3c4\uc2dc \uc774\ub984\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" } } }, diff --git a/homeassistant/components/meteo_france/translations/lb.json b/homeassistant/components/meteo_france/translations/lb.json index 310d53b1d8f..ea204d6092e 100644 --- a/homeassistant/components/meteo_france/translations/lb.json +++ b/homeassistant/components/meteo_france/translations/lb.json @@ -12,15 +12,13 @@ "data": { "city": "Stad" }, - "description": "Wiel deng Stad aus der L\u00ebscht aus", - "title": "M\u00e9t\u00e9o-France" + "description": "Wiel deng Stad aus der L\u00ebscht aus" }, "user": { "data": { "city": "Stad" }, - "description": "Gitt de Postcode an (n\u00ebmme fir Frankr\u00e4ich, recommand\u00e9iert) oder den Numm vun der Stad", - "title": "M\u00e9t\u00e9o-France" + "description": "Gitt de Postcode an (n\u00ebmme fir Frankr\u00e4ich, recommand\u00e9iert) oder den Numm vun der Stad" } } }, diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index 11b0f567776..5e36e0585c7 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -12,15 +12,13 @@ "data": { "city": "Stad" }, - "description": "Kies uw stad uit de lijst", - "title": "M\u00e9t\u00e9o-France" + "description": "Kies uw stad uit de lijst" }, "user": { "data": { "city": "Stad" }, - "description": "Vul de postcode (alleen voor Frankrijk, aanbevolen) of de plaatsnaam in", - "title": "M\u00e9t\u00e9o-France" + "description": "Vul de postcode (alleen voor Frankrijk, aanbevolen) of de plaatsnaam in" } } }, diff --git a/homeassistant/components/meteo_france/translations/no.json b/homeassistant/components/meteo_france/translations/no.json index e323d5426da..dea58523260 100644 --- a/homeassistant/components/meteo_france/translations/no.json +++ b/homeassistant/components/meteo_france/translations/no.json @@ -12,15 +12,13 @@ "data": { "city": "By" }, - "description": "Velg din by fra listen", - "title": "" + "description": "Velg din by fra listen" }, "user": { "data": { "city": "By" }, - "description": "Fyll inn postnummeret (bare for Frankrike, anbefalt) eller bynavn", - "title": "" + "description": "Fyll inn postnummeret (bare for Frankrike, anbefalt) eller bynavn" } } }, diff --git a/homeassistant/components/meteo_france/translations/pl.json b/homeassistant/components/meteo_france/translations/pl.json index 0030aa83865..c826a92ac41 100644 --- a/homeassistant/components/meteo_france/translations/pl.json +++ b/homeassistant/components/meteo_france/translations/pl.json @@ -12,15 +12,13 @@ "data": { "city": "Miasto" }, - "description": "Wybierz swoje miasto z listy", - "title": "M\u00e9t\u00e9o-France" + "description": "Wybierz swoje miasto z listy" }, "user": { "data": { "city": "Miasto" }, - "description": "Wprowad\u017a kod pocztowy (tylko dla Francji, zalecane) lub nazw\u0119 miasta", - "title": "M\u00e9t\u00e9o-France" + "description": "Wprowad\u017a kod pocztowy (tylko dla Francji, zalecane) lub nazw\u0119 miasta" } } }, diff --git a/homeassistant/components/meteo_france/translations/pt-BR.json b/homeassistant/components/meteo_france/translations/pt-BR.json index 456c6eef17c..9d5bb35f586 100644 --- a/homeassistant/components/meteo_france/translations/pt-BR.json +++ b/homeassistant/components/meteo_france/translations/pt-BR.json @@ -12,15 +12,13 @@ "data": { "city": "Cidade" }, - "description": "Escolha sua cidade na lista", - "title": "M\u00e9t\u00e9o-France" + "description": "Escolha sua cidade na lista" }, "user": { "data": { "city": "Cidade" }, - "description": "Insira o c\u00f3digo postal (somente para a Fran\u00e7a, recomendado) ou o nome da cidade", - "title": "M\u00e9t\u00e9o-France" + "description": "Insira o c\u00f3digo postal (somente para a Fran\u00e7a, recomendado) ou o nome da cidade" } } }, diff --git a/homeassistant/components/meteo_france/translations/pt.json b/homeassistant/components/meteo_france/translations/pt.json index f53975ecf00..d059033bdcb 100644 --- a/homeassistant/components/meteo_france/translations/pt.json +++ b/homeassistant/components/meteo_france/translations/pt.json @@ -8,8 +8,7 @@ "user": { "data": { "city": "Cidade" - }, - "title": "" + } } } } diff --git a/homeassistant/components/meteo_france/translations/ru.json b/homeassistant/components/meteo_france/translations/ru.json index 6f42dae3b49..6ef7a82849e 100644 --- a/homeassistant/components/meteo_france/translations/ru.json +++ b/homeassistant/components/meteo_france/translations/ru.json @@ -12,15 +12,13 @@ "data": { "city": "\u0413\u043e\u0440\u043e\u0434" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0433\u043e\u0440\u043e\u0434 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430", - "title": "M\u00e9t\u00e9o-France" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0433\u043e\u0440\u043e\u0434 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430" }, "user": { "data": { "city": "\u0413\u043e\u0440\u043e\u0434" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0424\u0440\u0430\u043d\u0446\u0438\u0438) \u0438\u043b\u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0433\u043e\u0440\u043e\u0434\u0430", - "title": "M\u00e9t\u00e9o-France" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0424\u0440\u0430\u043d\u0446\u0438\u0438) \u0438\u043b\u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0433\u043e\u0440\u043e\u0434\u0430" } } }, diff --git a/homeassistant/components/meteo_france/translations/sl.json b/homeassistant/components/meteo_france/translations/sl.json index 912748ada8f..6acfeb28e08 100644 --- a/homeassistant/components/meteo_france/translations/sl.json +++ b/homeassistant/components/meteo_france/translations/sl.json @@ -9,8 +9,7 @@ "data": { "city": "Mesto" }, - "description": "Vnesite po\u0161tno \u0161tevilko (samo za Francijo) ali ime mesta", - "title": "M\u00e9t\u00e9o-France" + "description": "Vnesite po\u0161tno \u0161tevilko (samo za Francijo) ali ime mesta" } } } diff --git a/homeassistant/components/meteo_france/translations/sv.json b/homeassistant/components/meteo_france/translations/sv.json index 70fb537fc82..f7f1a68478f 100644 --- a/homeassistant/components/meteo_france/translations/sv.json +++ b/homeassistant/components/meteo_france/translations/sv.json @@ -14,8 +14,7 @@ "data": { "city": "Stad" }, - "description": "Ange postnumret (endast f\u00f6r Frankrike, rekommenderat) eller ortsnamn", - "title": "M\u00e9t\u00e9o-France" + "description": "Ange postnumret (endast f\u00f6r Frankrike, rekommenderat) eller ortsnamn" } } } diff --git a/homeassistant/components/meteo_france/translations/tr.json b/homeassistant/components/meteo_france/translations/tr.json index 4793dbb4fe7..38e60b14b8e 100644 --- a/homeassistant/components/meteo_france/translations/tr.json +++ b/homeassistant/components/meteo_france/translations/tr.json @@ -12,15 +12,13 @@ "data": { "city": "\u015eehir" }, - "description": "Listeden \u015fehrinizi se\u00e7in", - "title": "M\u00e9t\u00e9o-Fransa" + "description": "Listeden \u015fehrinizi se\u00e7in" }, "user": { "data": { "city": "\u015eehir" }, - "description": "Posta kodunu (yaln\u0131zca Fransa i\u00e7in, \u00f6nerilir) veya \u015fehir ad\u0131n\u0131 girin", - "title": "M\u00e9t\u00e9o-Fransa" + "description": "Posta kodunu (yaln\u0131zca Fransa i\u00e7in, \u00f6nerilir) veya \u015fehir ad\u0131n\u0131 girin" } } }, diff --git a/homeassistant/components/meteo_france/translations/uk.json b/homeassistant/components/meteo_france/translations/uk.json index a84c230e218..124aacb9d67 100644 --- a/homeassistant/components/meteo_france/translations/uk.json +++ b/homeassistant/components/meteo_france/translations/uk.json @@ -12,15 +12,13 @@ "data": { "city": "\u041c\u0456\u0441\u0442\u043e" }, - "description": "\u041e\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0442\u043e \u0437\u0456 \u0441\u043f\u0438\u0441\u043a\u0443", - "title": "M\u00e9t\u00e9o-France" + "description": "\u041e\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0442\u043e \u0437\u0456 \u0441\u043f\u0438\u0441\u043a\u0443" }, "user": { "data": { "city": "\u041c\u0456\u0441\u0442\u043e" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0424\u0440\u0430\u043d\u0446\u0456\u0457) \u0430\u0431\u043e \u043d\u0430\u0437\u0432\u0443 \u043c\u0456\u0441\u0442\u0430", - "title": "M\u00e9t\u00e9o-France" + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0424\u0440\u0430\u043d\u0446\u0456\u0457) \u0430\u0431\u043e \u043d\u0430\u0437\u0432\u0443 \u043c\u0456\u0441\u0442\u0430" } } }, diff --git a/homeassistant/components/meteo_france/translations/zh-Hant.json b/homeassistant/components/meteo_france/translations/zh-Hant.json index 32d92956d73..2f1aa7fe89e 100644 --- a/homeassistant/components/meteo_france/translations/zh-Hant.json +++ b/homeassistant/components/meteo_france/translations/zh-Hant.json @@ -12,15 +12,13 @@ "data": { "city": "\u57ce\u5e02\u540d\u7a31" }, - "description": "\u7531\u5217\u8868\u4e2d\u9078\u64c7\u57ce\u5e02", - "title": "M\u00e9t\u00e9o-France" + "description": "\u7531\u5217\u8868\u4e2d\u9078\u64c7\u57ce\u5e02" }, "user": { "data": { "city": "\u57ce\u5e02\u540d\u7a31" }, - "description": "\u8f38\u5165\u90f5\u905e\u5340\u865f\uff08\u50c5\u652f\u63f4\u6cd5\u570b\uff09\u6216\u57ce\u5e02\u540d\u7a31", - "title": "M\u00e9t\u00e9o-France" + "description": "\u8f38\u5165\u90f5\u905e\u5340\u865f\uff08\u50c5\u652f\u63f4\u6cd5\u570b\uff09\u6216\u57ce\u5e02\u540d\u7a31" } } }, diff --git a/homeassistant/components/meteoclimatic/translations/bg.json b/homeassistant/components/meteoclimatic/translations/bg.json index 63b0e7be8b7..4bac01649f7 100644 --- a/homeassistant/components/meteoclimatic/translations/bg.json +++ b/homeassistant/components/meteoclimatic/translations/bg.json @@ -6,11 +6,6 @@ }, "error": { "not_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430" - }, - "step": { - "user": { - "title": "Meteoclimatic" - } } } } \ No newline at end of file diff --git a/homeassistant/components/meteoclimatic/translations/ca.json b/homeassistant/components/meteoclimatic/translations/ca.json index 5f672d87535..e7d760253e7 100644 --- a/homeassistant/components/meteoclimatic/translations/ca.json +++ b/homeassistant/components/meteoclimatic/translations/ca.json @@ -12,8 +12,9 @@ "data": { "code": "Codi d'estaci\u00f3" }, - "description": "Introdueix el codi d'estaci\u00f3 meteorol\u00f2gica (per exemple, ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "T\u00e9 el format com ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/de.json b/homeassistant/components/meteoclimatic/translations/de.json index c9b9ea90e61..7365dba674d 100644 --- a/homeassistant/components/meteoclimatic/translations/de.json +++ b/homeassistant/components/meteoclimatic/translations/de.json @@ -12,8 +12,9 @@ "data": { "code": "Stationscode" }, - "description": "Gib den Code der Meteoclimatic-Station ein (z. B. ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Sieht aus wie ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/el.json b/homeassistant/components/meteoclimatic/translations/el.json index 085e6fe1643..01854a07830 100644 --- a/homeassistant/components/meteoclimatic/translations/el.json +++ b/homeassistant/components/meteoclimatic/translations/el.json @@ -12,8 +12,9 @@ "data": { "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd" }, - "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd Meteoclimatic (\u03c0.\u03c7. ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "\u039c\u03bf\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/en.json b/homeassistant/components/meteoclimatic/translations/en.json index a066971be25..50a4795410d 100644 --- a/homeassistant/components/meteoclimatic/translations/en.json +++ b/homeassistant/components/meteoclimatic/translations/en.json @@ -12,8 +12,9 @@ "data": { "code": "Station code" }, - "description": "Enter the Meteoclimatic station code (e.g., ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Looks like ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/es.json b/homeassistant/components/meteoclimatic/translations/es.json index ab84e6604e3..027f85c60df 100644 --- a/homeassistant/components/meteoclimatic/translations/es.json +++ b/homeassistant/components/meteoclimatic/translations/es.json @@ -11,9 +11,7 @@ "user": { "data": { "code": "C\u00f3digo de la estaci\u00f3n" - }, - "description": "Introduzca el c\u00f3digo de la estaci\u00f3n Meteoclimatic (por ejemplo, ESCAT4300000043206B)", - "title": "Meteoclimatic" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/et.json b/homeassistant/components/meteoclimatic/translations/et.json index e019a4a0e12..886de9279f9 100644 --- a/homeassistant/components/meteoclimatic/translations/et.json +++ b/homeassistant/components/meteoclimatic/translations/et.json @@ -12,8 +12,9 @@ "data": { "code": "Jaama kood" }, - "description": "Sisesta Meteoclimatic jaama kood (nt ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "N\u00e4eb v\u00e4lja nagu ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/fr.json b/homeassistant/components/meteoclimatic/translations/fr.json index ae087db2e6e..95151d8f553 100644 --- a/homeassistant/components/meteoclimatic/translations/fr.json +++ b/homeassistant/components/meteoclimatic/translations/fr.json @@ -12,8 +12,9 @@ "data": { "code": "Code de la station" }, - "description": "Entrer le code de la station m\u00e9t\u00e9orologique (par exemple, ESCAT4300000043206)", - "title": "M\u00e9t\u00e9oclimatique" + "data_description": { + "code": "Ressemble \u00e0 ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/gl.json b/homeassistant/components/meteoclimatic/translations/gl.json deleted file mode 100644 index be1629dd6e4..00000000000 --- a/homeassistant/components/meteoclimatic/translations/gl.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "description": "Introduza o c\u00f3digo da estaci\u00f3n Meteoclimatic (por exemplo, ESCAT4300000043206B)" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/meteoclimatic/translations/hu.json b/homeassistant/components/meteoclimatic/translations/hu.json index 582c78f263d..d9583656eb2 100644 --- a/homeassistant/components/meteoclimatic/translations/hu.json +++ b/homeassistant/components/meteoclimatic/translations/hu.json @@ -12,8 +12,9 @@ "data": { "code": "\u00c1llom\u00e1s k\u00f3dja" }, - "description": "Adja meg a meteorol\u00f3giai \u00e1llom\u00e1s k\u00f3dj\u00e1t (pl. ESCAT4300000043206B)", - "title": "Meteoklimatikus" + "data_description": { + "code": "\u00dagy n\u00e9z ki, mint ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/id.json b/homeassistant/components/meteoclimatic/translations/id.json index 4e23dad8dc4..3f8221f059a 100644 --- a/homeassistant/components/meteoclimatic/translations/id.json +++ b/homeassistant/components/meteoclimatic/translations/id.json @@ -12,8 +12,9 @@ "data": { "code": "Kode stasiun" }, - "description": "Masukkan kode stasiun Meteoclimatic (misalnya, ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Sepertinya ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/it.json b/homeassistant/components/meteoclimatic/translations/it.json index fcdfa25c496..f069b393a1b 100644 --- a/homeassistant/components/meteoclimatic/translations/it.json +++ b/homeassistant/components/meteoclimatic/translations/it.json @@ -12,8 +12,9 @@ "data": { "code": "Codice della stazione" }, - "description": "Immettere il codice della stazione Meteoclimatic (ad esempio ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Assomiglia a ECAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/ja.json b/homeassistant/components/meteoclimatic/translations/ja.json index 0274ff70e88..adae157aeee 100644 --- a/homeassistant/components/meteoclimatic/translations/ja.json +++ b/homeassistant/components/meteoclimatic/translations/ja.json @@ -12,8 +12,9 @@ "data": { "code": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u30b3\u30fc\u30c9" }, - "description": "Meteoclimatic\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u30b3\u30fc\u30c9\u3092\u5165\u529b(\u4f8b: ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "ESCAT4300000043206B\u306e\u3088\u3046\u306b\u898b\u3048\u307e\u3059" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/nl.json b/homeassistant/components/meteoclimatic/translations/nl.json index dd2a318ec37..c6f7151ade6 100644 --- a/homeassistant/components/meteoclimatic/translations/nl.json +++ b/homeassistant/components/meteoclimatic/translations/nl.json @@ -12,8 +12,9 @@ "data": { "code": "Station code" }, - "description": "Voer de code van het meteoklimatologische station in (bv. ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Lijkt op ESCAT43000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/no.json b/homeassistant/components/meteoclimatic/translations/no.json index 0e6e080d146..1bcf4ae09fe 100644 --- a/homeassistant/components/meteoclimatic/translations/no.json +++ b/homeassistant/components/meteoclimatic/translations/no.json @@ -12,8 +12,9 @@ "data": { "code": "Stasjonskode" }, - "description": "Angi Meteoclimatic stasjonskode (f.eks. ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Ser ut som ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/pl.json b/homeassistant/components/meteoclimatic/translations/pl.json index c4539bd6c8b..aab41ba7d12 100644 --- a/homeassistant/components/meteoclimatic/translations/pl.json +++ b/homeassistant/components/meteoclimatic/translations/pl.json @@ -12,8 +12,9 @@ "data": { "code": "Kod stacji" }, - "description": "Wpisz kod stacji Meteoclimatic (np. ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "Wygl\u0105da jak ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/pt-BR.json b/homeassistant/components/meteoclimatic/translations/pt-BR.json index c81109b8939..88223d4b177 100644 --- a/homeassistant/components/meteoclimatic/translations/pt-BR.json +++ b/homeassistant/components/meteoclimatic/translations/pt-BR.json @@ -12,8 +12,9 @@ "data": { "code": "C\u00f3digo da esta\u00e7\u00e3o" }, - "description": "Digite o c\u00f3digo da esta\u00e7\u00e3o Meteoclim\u00e1tica (por exemplo, ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "\u00c9 parecido com isso ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/pt.json b/homeassistant/components/meteoclimatic/translations/pt.json deleted file mode 100644 index 71eded9f5de..00000000000 --- a/homeassistant/components/meteoclimatic/translations/pt.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "description": "Introduza o c\u00f3digo da esta\u00e7\u00e3o Meteoclimatic (por exemplo, ESCAT4300000043206B)" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/meteoclimatic/translations/ru.json b/homeassistant/components/meteoclimatic/translations/ru.json index 14df114c903..79421f0f325 100644 --- a/homeassistant/components/meteoclimatic/translations/ru.json +++ b/homeassistant/components/meteoclimatic/translations/ru.json @@ -12,8 +12,9 @@ "data": { "code": "\u041a\u043e\u0434 \u0441\u0442\u0430\u043d\u0446\u0438\u0438" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 Meteoclimatic (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/tr.json b/homeassistant/components/meteoclimatic/translations/tr.json index a56fe984e2e..5221bc971c0 100644 --- a/homeassistant/components/meteoclimatic/translations/tr.json +++ b/homeassistant/components/meteoclimatic/translations/tr.json @@ -12,8 +12,9 @@ "data": { "code": "\u0130stasyon kodu" }, - "description": "Meteoclimatic istasyon kodunu girin (\u00f6rne\u011fin, ESCAT4300000043206B)", - "title": "Meteoclimatic" + "data_description": { + "code": "ESCAT4300000043206B'ye benziyor" + } } } } diff --git a/homeassistant/components/meteoclimatic/translations/zh-Hant.json b/homeassistant/components/meteoclimatic/translations/zh-Hant.json index d5c3793be7d..7aa165509f2 100644 --- a/homeassistant/components/meteoclimatic/translations/zh-Hant.json +++ b/homeassistant/components/meteoclimatic/translations/zh-Hant.json @@ -12,8 +12,9 @@ "data": { "code": "\u6c23\u8c61\u7ad9\u4ee3\u78bc" }, - "description": "\u8f38\u5165 Meteoclimatic \u6c23\u8c61\u7ad9\u4ee3\u78bc\uff08\u4f8b\u5982 ESCAT4300000043206B\uff09", - "title": "Meteoclimatic" + "data_description": { + "code": "\u770b\u8d77\u4f86\u985e\u4f3c ESCAT4300000043206B" + } } } } diff --git a/homeassistant/components/mikrotik/translations/hu.json b/homeassistant/components/mikrotik/translations/hu.json index 3e5281fc06a..c15ba2f07aa 100644 --- a/homeassistant/components/mikrotik/translations/hu.json +++ b/homeassistant/components/mikrotik/translations/hu.json @@ -12,7 +12,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index 4a06e597c36..e7cd297bd59 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -4,13 +4,11 @@ import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, + FAN_OFF, FAN_ON, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -88,7 +86,7 @@ async def async_setup_entry( class MillHeater(CoordinatorEntity, ClimateEntity): """Representation of a Mill Thermostat device.""" - _attr_fan_modes = [FAN_ON, HVAC_MODE_OFF] + _attr_fan_modes = [FAN_ON, FAN_OFF] _attr_max_temp = MAX_TEMP _attr_min_temp = MIN_TEMP _attr_target_temperature_step = PRECISION_WHOLE @@ -111,16 +109,16 @@ class MillHeater(CoordinatorEntity, ClimateEntity): name=self.name, ) if heater.is_gen1: - self._attr_hvac_modes = [HVAC_MODE_HEAT] + self._attr_hvac_modes = [HVACMode.HEAT] else: - self._attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF] + self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] if heater.generation < 3: self._attr_supported_features = ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE ) else: - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE self._update_attr(heater) @@ -141,16 +139,16 @@ class MillHeater(CoordinatorEntity, ClimateEntity): ) await self.coordinator.async_request_refresh() - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" heater = self.coordinator.data[self._id] - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: await self.coordinator.mill_data_connection.heater_control( self._id, power_status=1 ) await self.coordinator.async_request_refresh() - elif hvac_mode == HVAC_MODE_OFF and not heater.is_gen1: + elif hvac_mode == HVACMode.OFF and not heater.is_gen1: await self.coordinator.mill_data_connection.heater_control( self._id, power_status=0 ) @@ -183,25 +181,25 @@ class MillHeater(CoordinatorEntity, ClimateEntity): self._attr_extra_state_attributes["room"] = "Independent device" self._attr_target_temperature = heater.set_temp self._attr_current_temperature = heater.current_temp - self._attr_fan_mode = FAN_ON if heater.fan_status == 1 else HVAC_MODE_OFF + self._attr_fan_mode = FAN_ON if heater.fan_status == 1 else HVACMode.OFF if heater.is_heating == 1: - self._attr_hvac_action = CURRENT_HVAC_HEAT + self._attr_hvac_action = HVACAction.HEATING else: - self._attr_hvac_action = CURRENT_HVAC_IDLE + self._attr_hvac_action = HVACAction.IDLE if heater.is_gen1 or heater.power_status == 1: - self._attr_hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_mode = HVACMode.HEAT else: - self._attr_hvac_mode = HVAC_MODE_OFF + self._attr_hvac_mode = HVACMode.OFF class LocalMillHeater(CoordinatorEntity, ClimateEntity): """Representation of a Mill Thermostat device.""" - _attr_hvac_mode = HVAC_MODE_HEAT - _attr_hvac_modes = [HVAC_MODE_HEAT] + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] _attr_max_temp = MAX_TEMP _attr_min_temp = MIN_TEMP - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_target_temperature_step = PRECISION_WHOLE _attr_temperature_unit = TEMP_CELSIUS @@ -244,6 +242,6 @@ class LocalMillHeater(CoordinatorEntity, ClimateEntity): self._attr_current_temperature = data["ambient_temperature"] if data["current_power"] > 0: - self._attr_hvac_action = CURRENT_HVAC_HEAT + self._attr_hvac_action = HVACAction.HEATING else: - self._attr_hvac_action = CURRENT_HVAC_IDLE + self._attr_hvac_action = HVACAction.IDLE diff --git a/homeassistant/components/min_max/config_flow.py b/homeassistant/components/min_max/config_flow.py index 6dab7cc000c..2114a5406d0 100644 --- a/homeassistant/components/min_max/config_flow.py +++ b/homeassistant/components/min_max/config_flow.py @@ -17,31 +17,33 @@ from homeassistant.helpers.schema_config_entry_flow import ( from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN _STATISTIC_MEASURES = [ - {"value": "min", "label": "Minimum"}, - {"value": "max", "label": "Maximum"}, - {"value": "mean", "label": "Arithmetic mean"}, - {"value": "median", "label": "Median"}, - {"value": "last", "label": "Most recently updated"}, + selector.SelectOptionDict(value="min", label="Minimum"), + selector.SelectOptionDict(value="max", label="Maximum"), + selector.SelectOptionDict(value="mean", label="Arithmetic mean"), + selector.SelectOptionDict(value="median", label="Median"), + selector.SelectOptionDict(value="last", label="Most recently updated"), ] OPTIONS_SCHEMA = vol.Schema( { - vol.Required(CONF_ENTITY_IDS): selector.selector( - {"entity": {"domain": "sensor", "multiple": True}} + vol.Required(CONF_ENTITY_IDS): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor", multiple=True), ), - vol.Required(CONF_TYPE): selector.selector( - {"select": {"options": _STATISTIC_MEASURES}} + vol.Required(CONF_TYPE): selector.SelectSelector( + selector.SelectSelectorConfig(options=_STATISTIC_MEASURES), ), - vol.Required(CONF_ROUND_DIGITS, default=2): selector.selector( - {"number": {"min": 0, "max": 6, "mode": "box"}} + vol.Required(CONF_ROUND_DIGITS, default=2): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=6, mode=selector.NumberSelectorMode.BOX + ), ), } ) CONFIG_SCHEMA = vol.Schema( { - vol.Required("name"): selector.selector({"text": {}}), + vol.Required("name"): selector.TextSelector(), } ).extend(OPTIONS_SCHEMA.schema) diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index bfc6b99d150..5c117357729 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +import statistics import voluptuous as vol @@ -30,24 +31,11 @@ ATTR_MIN_VALUE = "min_value" ATTR_MIN_ENTITY_ID = "min_entity_id" ATTR_MAX_VALUE = "max_value" ATTR_MAX_ENTITY_ID = "max_entity_id" -ATTR_COUNT_SENSORS = "count_sensors" ATTR_MEAN = "mean" ATTR_MEDIAN = "median" ATTR_LAST = "last" ATTR_LAST_ENTITY_ID = "last_entity_id" -ATTR_TO_PROPERTY = [ - ATTR_COUNT_SENSORS, - ATTR_MAX_VALUE, - ATTR_MAX_ENTITY_ID, - ATTR_MEAN, - ATTR_MEDIAN, - ATTR_MIN_VALUE, - ATTR_MIN_ENTITY_ID, - ATTR_LAST, - ATTR_LAST_ENTITY_ID, -] - ICON = "mdi:calculator" SENSOR_TYPES = { @@ -57,6 +45,7 @@ SENSOR_TYPES = { ATTR_MEDIAN: "median", ATTR_LAST: "last", } +SENSOR_TYPE_TO_ATTR = {v: k for k, v in SENSOR_TYPES.items()} PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -149,7 +138,7 @@ def calc_mean(sensor_values, round_digits): if not result: return None - return round(sum(result) / len(result), round_digits) + return round(statistics.mean(result), round_digits) def calc_median(sensor_values, round_digits): @@ -162,14 +151,7 @@ def calc_median(sensor_values, round_digits): if not result: return None - result.sort() - if len(result) % 2 == 0: - median1 = result[len(result) // 2] - median2 = result[len(result) // 2 - 1] - median = (median1 + median2) / 2 - else: - median = result[len(result) // 2] - return round(median, round_digits) + return round(statistics.median(result), round_digits) class MinMaxSensor(SensorEntity): @@ -185,7 +167,8 @@ class MinMaxSensor(SensorEntity): if name: self._name = name else: - self._name = f"{next(v for k, v in SENSOR_TYPES.items() if self._sensor_type == v)} sensor".capitalize() + self._name = f"{sensor_type} sensor".capitalize() + self._sensor_attr = SENSOR_TYPE_TO_ATTR[self._sensor_type] self._unit_of_measurement = None self._unit_of_measurement_mismatch = False self.min_value = self.max_value = self.mean = self.last = self.median = None @@ -219,9 +202,7 @@ class MinMaxSensor(SensorEntity): """Return the state of the sensor.""" if self._unit_of_measurement_mismatch: return None - return getattr( - self, next(k for k, v in SENSOR_TYPES.items() if self._sensor_type == v) - ) + return getattr(self, self._sensor_attr) @property def native_unit_of_measurement(self): @@ -238,11 +219,13 @@ class MinMaxSensor(SensorEntity): @property def extra_state_attributes(self): """Return the state attributes of the sensor.""" - return { - attr: getattr(self, attr) - for attr in ATTR_TO_PROPERTY - if getattr(self, attr) is not None - } + if self._sensor_type == "min": + return {ATTR_MIN_ENTITY_ID: self.min_entity_id} + if self._sensor_type == "max": + return {ATTR_MAX_ENTITY_ID: self.max_entity_id} + if self._sensor_type == "last": + return {ATTR_LAST_ENTITY_ID: self.last_entity_id} + return None @property def icon(self): diff --git a/homeassistant/components/vallox/translations/sk.json b/homeassistant/components/min_max/translations/bg.json similarity index 73% rename from homeassistant/components/vallox/translations/sk.json rename to homeassistant/components/min_max/translations/bg.json index af15f92c2f2..35cfa0ad1d7 100644 --- a/homeassistant/components/vallox/translations/sk.json +++ b/homeassistant/components/min_max/translations/bg.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "name": "N\u00e1zov" + "name": "\u0418\u043c\u0435" } } } diff --git a/homeassistant/components/min_max/translations/ca.json b/homeassistant/components/min_max/translations/ca.json new file mode 100644 index 00000000000..b114e8ecf40 --- /dev/null +++ b/homeassistant/components/min_max/translations/ca.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entitats d'entrada", + "name": "Nom", + "round_digits": "Precisi\u00f3", + "type": "Caracter\u00edstica estad\u00edstica" + }, + "data_description": { + "round_digits": "Controla el nombre de d\u00edgits decimals quan la caracter\u00edstica estad\u00edstica \u00e9s la mitjana o la mediana." + }, + "description": "Crea un sensor que calcula el m\u00ednim, m\u00e0xim, la mitjana o la mediana d'una llista de sensors d'entrada.", + "title": "Afegeix sensor m\u00edn / m\u00e0x / mitjana / mediana" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entitats d'entrada", + "round_digits": "Precisi\u00f3", + "type": "Caracter\u00edstica estad\u00edstica" + }, + "data_description": { + "round_digits": "Controla el nombre de d\u00edgits decimals quan la caracter\u00edstica estad\u00edstica \u00e9s la mitjana o la mediana." + } + }, + "options": { + "data": { + "entity_ids": "Entitats d'entrada", + "round_digits": "Precisi\u00f3", + "type": "Caracter\u00edstica estad\u00edstica" + }, + "description": "Crea un sensor que calcula el m\u00ednim, m\u00e0xim, la mitjana o la mediana d'una llista de sensors d'entrada." + } + } + }, + "title": "Sensor m\u00edn / m\u00e0x / mitjana / mediana" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/cs.json b/homeassistant/components/min_max/translations/cs.json new file mode 100644 index 00000000000..a969a39234a --- /dev/null +++ b/homeassistant/components/min_max/translations/cs.json @@ -0,0 +1,34 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Vstupn\u00ed entity", + "name": "Jm\u00e9no", + "round_digits": "P\u0159esnost", + "type": "Statistika" + }, + "description": "P\u0159esnost ur\u010duje po\u010det desetinn\u00fdch m\u00edst, kdy\u017e je statistikou pr\u016fm\u011br nebo medi\u00e1n." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Vstupn\u00ed entity", + "round_digits": "P\u0159esnost", + "type": "Statistika" + } + }, + "options": { + "data": { + "entity_ids": "Vstupn\u00ed entity", + "round_digits": "P\u0159esnost", + "type": "Statistika" + }, + "description": "P\u0159esnost ur\u010duje po\u010det desetinn\u00fdch m\u00edst, kdy\u017e je statistikou pr\u016fm\u011br nebo medi\u00e1n." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/de.json b/homeassistant/components/min_max/translations/de.json new file mode 100644 index 00000000000..c8b9d5ddf63 --- /dev/null +++ b/homeassistant/components/min_max/translations/de.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Eingabe-Entit\u00e4ten", + "name": "Name", + "round_digits": "Genauigkeit", + "type": "Statistisches Merkmal" + }, + "data_description": { + "round_digits": "Steuert die Anzahl der Dezimalstellen in der Ausgabe, wenn das Statistikmerkmal Mittelwert oder Median ist." + }, + "description": "Erstelle einen Sensor, der einen Mindest-, H\u00f6chst-, Mittel- oder Medianwert aus einer Liste von Eingabesensoren berechnet.", + "title": "Min/Max/Mittelwert/Median-Sensor hinzuf\u00fcgen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Eingabe-Entit\u00e4ten", + "round_digits": "Genauigkeit", + "type": "Statistisches Merkmal" + }, + "data_description": { + "round_digits": "Steuert die Anzahl der Dezimalstellen in der Ausgabe, wenn das Statistikmerkmal Mittelwert oder Median ist." + } + }, + "options": { + "data": { + "entity_ids": "Eingabe-Entit\u00e4ten", + "round_digits": "Genauigkeit", + "type": "Statistisches Merkmal" + }, + "description": "Erstelle einen Sensor, der einen Mindest-, H\u00f6chst-, Mittel- oder Medianwert aus einer Liste von Eingabesensoren berechnet." + } + } + }, + "title": "Min / Max / Mittelwert / Mediansensor" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/el.json b/homeassistant/components/min_max/translations/el.json new file mode 100644 index 00000000000..218c46fd683 --- /dev/null +++ b/homeassistant/components/min_max/translations/el.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "name": "\u039f\u03bd\u03bf\u03bc\u03b1", + "round_digits": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "type": "\u03a3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc" + }, + "data_description": { + "round_digits": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf \u03cc\u03c4\u03b1\u03bd \u03c4\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03ae \u03b4\u03b9\u03ac\u03bc\u03b5\u03c3\u03bf\u03c2." + }, + "description": "\u0397 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03cc\u03c4\u03b1\u03bd \u03c4\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03ae \u03b7 \u03b4\u03b9\u03ac\u03bc\u03b5\u03c3\u03bf\u03c2.", + "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 min / max / mean / median" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "round_digits": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "type": "\u03a3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc" + }, + "data_description": { + "round_digits": "\u0395\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03c3\u03c4\u03b7\u03bd \u03ad\u03be\u03bf\u03b4\u03bf \u03cc\u03c4\u03b1\u03bd \u03c4\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03ad\u03c3\u03bf\u03c2 \u03ae \u03b4\u03b9\u03ac\u03bc\u03b5\u03c3\u03bf\u03c2." + } + }, + "options": { + "data": { + "entity_ids": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "round_digits": "\u0391\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1", + "type": "\u03a3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc" + }, + "description": "\u0397 \u03b1\u03ba\u03c1\u03af\u03b2\u03b5\u03b9\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03b4\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03ce\u03bd \u03c8\u03b7\u03c6\u03af\u03c9\u03bd \u03cc\u03c4\u03b1\u03bd \u03c4\u03bf \u03c3\u03c4\u03b1\u03c4\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf \u03bc\u03ad\u03c3\u03bf\u03c2 \u03cc\u03c1\u03bf\u03c2 \u03ae \u03b7 \u03b4\u03b9\u03ac\u03bc\u03b5\u03c3\u03bf\u03c2." + } + } + }, + "title": "\u0395\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03c2 / \u03bc\u03ad\u03b3\u03b9\u03c3\u03c4\u03bf\u03c2 / \u03bc\u03ad\u03c3\u03bf\u03c2 / \u03b4\u03b9\u03ac\u03bc\u03b5\u03c3\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/en.json b/homeassistant/components/min_max/translations/en.json index 8cc0d41c419..b5b6eb5d731 100644 --- a/homeassistant/components/min_max/translations/en.json +++ b/homeassistant/components/min_max/translations/en.json @@ -27,6 +27,14 @@ "data_description": { "round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean or median." } + }, + "options": { + "data": { + "entity_ids": "Input entities", + "round_digits": "Precision", + "type": "Statistic characteristic" + }, + "description": "Create a sensor that calculates a min, max, mean or median value from a list of input sensors." } } }, diff --git a/homeassistant/components/min_max/translations/et.json b/homeassistant/components/min_max/translations/et.json new file mode 100644 index 00000000000..2f1689e8577 --- /dev/null +++ b/homeassistant/components/min_max/translations/et.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Sisendolemid", + "name": "Nimi", + "round_digits": "T\u00e4psus", + "type": "Statistiline tunnus" + }, + "data_description": { + "round_digits": "M\u00e4\u00e4rab k\u00fcmnendkohtade arvu v\u00e4ljundis kui statistika tunnus on keskmine v\u00f5i mediaan." + }, + "description": "Loo andur mis arvutab sisendandurite loendist minimaalse, maksimaalse, keskmise v\u00f5i mediaanv\u00e4\u00e4rtuse.", + "title": "Lisa min / max / keskmine / mediaanandur" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Sisendolemid", + "round_digits": "T\u00e4psus", + "type": "Statistiline omadus" + }, + "data_description": { + "round_digits": "M\u00e4\u00e4rab k\u00fcmnendkohtade arvu v\u00e4ljundis kui statistika tunnus on keskmine v\u00f5i mediaan." + } + }, + "options": { + "data": { + "entity_ids": "Sisendolemid", + "round_digits": "T\u00e4psus", + "type": "Statistiline tunnus" + }, + "description": "T\u00e4psus kontrollib k\u00fcmnendnumbrite arvu kui statistika tunnus on keskmine v\u00f5i mediaan." + } + } + }, + "title": "Min / max / keskmine / mediaanandur" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/fr.json b/homeassistant/components/min_max/translations/fr.json new file mode 100644 index 00000000000..b42ccf0bb2a --- /dev/null +++ b/homeassistant/components/min_max/translations/fr.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entit\u00e9s d'entr\u00e9e", + "name": "Nom", + "round_digits": "Pr\u00e9cision", + "type": "Indicateur statistique" + }, + "data_description": { + "round_digits": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux de la sortie lorsque l'indicateur statistique est la moyenne ou la m\u00e9diane." + }, + "description": "Cr\u00e9ez un capteur qui calcule une valeur minimale, maximale, moyenne ou m\u00e9diane \u00e0 partir d'une liste de capteurs d'entr\u00e9e.", + "title": "Ajouter un capteur de valeur minimale, maximale, moyenne ou m\u00e9diane" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entit\u00e9s d'entr\u00e9e", + "round_digits": "Pr\u00e9cision", + "type": "Indicateur statistique" + }, + "data_description": { + "round_digits": "Contr\u00f4le le nombre de chiffres d\u00e9cimaux de la sortie lorsque l'indicateur statistique est la moyenne ou la m\u00e9diane." + } + }, + "options": { + "data": { + "entity_ids": "Entit\u00e9s d'entr\u00e9e", + "round_digits": "Pr\u00e9cision", + "type": "Indicateur statistique" + }, + "description": "Cr\u00e9ez un capteur qui calcule une valeur minimale, maximale, moyenne ou m\u00e9diane \u00e0 partir d'une liste de capteurs d'entr\u00e9e." + } + } + }, + "title": "Capteur de valeur minimale, maximale, moyenne ou m\u00e9diane" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/he.json b/homeassistant/components/min_max/translations/he.json new file mode 100644 index 00000000000..267e38f0ce2 --- /dev/null +++ b/homeassistant/components/min_max/translations/he.json @@ -0,0 +1,37 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05e7\u05dc\u05d8", + "round_digits": "\u05d3\u05d9\u05d5\u05e7", + "type": "\u05de\u05d0\u05e4\u05d9\u05d9\u05df \u05e1\u05d8\u05d8\u05d9\u05e1\u05d8\u05d9\u05e7\u05d4" + }, + "data_description": { + "round_digits": "\u05e9\u05d5\u05dc\u05d8 \u05d1\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05d4\u05e2\u05e9\u05e8\u05d5\u05e0\u05d9\u05d5\u05ea \u05d1\u05e4\u05dc\u05d8 \u05db\u05d0\u05e9\u05e8 \u05de\u05d0\u05e4\u05d9\u05d9\u05df \u05d4\u05e1\u05d8\u05d8\u05d9\u05e1\u05d8\u05d9\u05e7\u05d4 \u05d4\u05d5\u05d0 \u05de\u05de\u05d5\u05e6\u05e2 \u05d0\u05d5 \u05d7\u05e6\u05d9\u05d5\u05e0\u05d9." + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05e7\u05dc\u05d8", + "round_digits": "\u05d3\u05d9\u05d5\u05e7", + "type": "\u05de\u05d0\u05e4\u05d9\u05d9\u05df \u05e1\u05d8\u05d8\u05d9\u05e1\u05d8\u05d9\u05e7\u05d4" + }, + "data_description": { + "round_digits": "\u05e9\u05d5\u05dc\u05d8 \u05d1\u05de\u05e1\u05e4\u05e8 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05d4\u05e2\u05e9\u05e8\u05d5\u05e0\u05d9\u05d5\u05ea \u05d1\u05e4\u05dc\u05d8 \u05db\u05d0\u05e9\u05e8 \u05de\u05d0\u05e4\u05d9\u05d9\u05df \u05d4\u05e1\u05d8\u05d8\u05d9\u05e1\u05d8\u05d9\u05e7\u05d4 \u05d4\u05d5\u05d0 \u05de\u05de\u05d5\u05e6\u05e2 \u05d0\u05d5 \u05d7\u05e6\u05d9\u05d5\u05e0\u05d9." + } + }, + "options": { + "data": { + "entity_ids": "\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05e7\u05dc\u05d8", + "round_digits": "\u05d3\u05d9\u05d5\u05e7", + "type": "\u05de\u05d0\u05e4\u05d9\u05d9\u05df \u05e1\u05d8\u05d8\u05d9\u05e1\u05d8\u05d9\u05e7\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/hu.json b/homeassistant/components/min_max/translations/hu.json new file mode 100644 index 00000000000..98d021dbb31 --- /dev/null +++ b/homeassistant/components/min_max/translations/hu.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Forr\u00e1s entit\u00e1sok", + "name": "Elnevez\u00e9s", + "round_digits": "Pontoss\u00e1g", + "type": "Statisztikai jellemz\u0151" + }, + "data_description": { + "round_digits": "Szab\u00e1lyozza a tizedesjegyek sz\u00e1m\u00e1t, amikor a statisztikai jellemz\u0151 az \u00e1tlag vagy a medi\u00e1n." + }, + "description": "A pontoss\u00e1g a tizedesjegyek sz\u00e1m\u00e1t szab\u00e1lyozza, ha a statisztikai jellemz\u0151 az \u00e1tlag vagy a medi\u00e1n.", + "title": "Min / max / \u00e1tlag / medi\u00e1n \u00e9rz\u00e9kel\u0151 hozz\u00e1ad\u00e1sa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Forr\u00e1s entit\u00e1sok", + "round_digits": "Pontoss\u00e1g", + "type": "Statisztikai jellemz\u0151" + }, + "data_description": { + "round_digits": "Szab\u00e1lyozza a tizedesjegyek sz\u00e1m\u00e1t, amikor a statisztikai jellemz\u0151 az \u00e1tlag vagy a medi\u00e1n." + } + }, + "options": { + "data": { + "entity_ids": "Forr\u00e1s entit\u00e1sok", + "round_digits": "Pontoss\u00e1g", + "type": "Statisztikai jellemz\u0151" + }, + "description": "A pontoss\u00e1g a tizedesjegyek sz\u00e1m\u00e1t szab\u00e1lyozza, ha a statisztikai jellemz\u0151 az \u00e1tlag vagy a medi\u00e1n." + } + } + }, + "title": "Min / max / \u00e1tlag / medi\u00e1n \u00e9rz\u00e9kel\u0151" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/id.json b/homeassistant/components/min_max/translations/id.json new file mode 100644 index 00000000000..32ccfd2596d --- /dev/null +++ b/homeassistant/components/min_max/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entitas input", + "name": "Nama", + "round_digits": "Presisi", + "type": "Karakteristik statistik" + }, + "data_description": { + "round_digits": "Mengontrol jumlah digit desimal dalam output ketika karakteristik statistik adalah rata-rata atau median." + }, + "description": "Buat sensor yang menghitung nilai min, maks, rata-rata, atau median dari sejumlah sensor input.", + "title": "Tambahkan sensor min/maks/rata-rata/median" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entitas input", + "round_digits": "Presisi", + "type": "Karakteristik statistik" + }, + "data_description": { + "round_digits": "Mengontrol jumlah digit desimal dalam output ketika karakteristik statistik adalah rata-rata atau median." + } + }, + "options": { + "data": { + "entity_ids": "Entitas input", + "round_digits": "Presisi", + "type": "Karakteristik statistik" + }, + "description": "Buat sensor yang menghitung nilai min, maks, rata-rata, atau median dari sejumlah sensor input." + } + } + }, + "title": "Sensor min/maks/rata-rata/median" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/it.json b/homeassistant/components/min_max/translations/it.json new file mode 100644 index 00000000000..49c715a7507 --- /dev/null +++ b/homeassistant/components/min_max/translations/it.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entit\u00e0 di ingresso", + "name": "Nome", + "round_digits": "Precisione", + "type": "Caratteristica statistica" + }, + "data_description": { + "round_digits": "Controlla il numero di cifre decimali nell'output quando la caratteristica statistica \u00e8 media o mediana." + }, + "description": "Crea un sensore che calcoli un valore minimo, massimo, medio o mediano da un elenco di sensori di input.", + "title": "Aggiungi sensore min / max / media / mediana" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entit\u00e0 di ingresso", + "round_digits": "Precisione", + "type": "Caratteristica statistica" + }, + "data_description": { + "round_digits": "Controlla il numero di cifre decimali nell'output quando la caratteristica statistica \u00e8 media o mediana." + } + }, + "options": { + "data": { + "entity_ids": "Entit\u00e0 di ingresso", + "round_digits": "Precisione", + "type": "Caratteristica statistica" + }, + "description": "Crea un sensore che calcoli un valore minimo, massimo, medio o mediano da un elenco di sensori di input." + } + } + }, + "title": "Sensore min / max / media / mediana" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/ja.json b/homeassistant/components/min_max/translations/ja.json new file mode 100644 index 00000000000..c853a7c389e --- /dev/null +++ b/homeassistant/components/min_max/translations/ja.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\u5165\u529b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "name": "\u540d\u524d", + "round_digits": "\u7cbe\u5ea6", + "type": "\u7d71\u8a08\u7684\u7279\u6027" + }, + "data_description": { + "round_digits": "\u7d71\u8a08\u7684\u7279\u6027\u304c\u5e73\u5747\u5024\u307e\u305f\u306f\u4e2d\u592e\u5024\u306e\u5834\u5408\u306b\u3001\u51fa\u529b\u306b\u542b\u307e\u308c\u308b\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002" + }, + "description": "\u7d71\u8a08\u7684\u7279\u6027\u304c\u5e73\u5747\u307e\u305f\u306f\u4e2d\u592e\u5024\u306a\u5834\u5408\u306e\u7cbe\u5ea6\u3067\u3001\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002", + "title": "\u6700\u5c0f/\u6700\u5927/\u5e73\u5747/\u4e2d\u592e\u5024 \u30bb\u30f3\u30b5\u30fc\u3092\u8ffd\u52a0" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\u5165\u529b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "round_digits": "\u7cbe\u5ea6", + "type": "\u7d71\u8a08\u7684\u7279\u6027" + }, + "data_description": { + "round_digits": "\u7d71\u8a08\u7684\u7279\u6027\u304c\u5e73\u5747\u5024\u307e\u305f\u306f\u4e2d\u592e\u5024\u306e\u5834\u5408\u306b\u3001\u51fa\u529b\u306b\u542b\u307e\u308c\u308b\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002" + } + }, + "options": { + "data": { + "entity_ids": "\u5165\u529b\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "round_digits": "\u7cbe\u5ea6", + "type": "\u7d71\u8a08\u7684\u7279\u6027" + }, + "description": "\u7d71\u8a08\u7684\u7279\u6027\u304c\u5e73\u5747\u307e\u305f\u306f\u4e2d\u592e\u5024\u306a\u5834\u5408\u306e\u7cbe\u5ea6\u3067\u3001\u5c0f\u6570\u70b9\u4ee5\u4e0b\u306e\u6841\u6570\u3092\u5236\u5fa1\u3057\u307e\u3059\u3002" + } + } + }, + "title": "\u6700\u5c0f/\u6700\u5927/\u5e73\u5747/\u4e2d\u592e\u5024 \u30bb\u30f3\u30b5\u30fc" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/nl.json b/homeassistant/components/min_max/translations/nl.json new file mode 100644 index 00000000000..36f09f97d54 --- /dev/null +++ b/homeassistant/components/min_max/translations/nl.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entiteiten invoeren", + "name": "Naam", + "round_digits": "Precisie", + "type": "statistisch kenmerk" + }, + "data_description": { + "round_digits": "Regelt het aantal decimale cijfers in de uitvoer wanneer de statistische eigenschap gemiddelde of mediaan is." + }, + "description": "Maak een sensor die een min, max, gemiddelde of mediaanwaarde berekent uit een lijst van invoersensoren.", + "title": "Voeg min / max / gemiddelde / mediaan sensor toe" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entiteiten invoeren", + "round_digits": "Precisie", + "type": "statistisch kenmerk" + }, + "data_description": { + "round_digits": "Regelt het aantal decimale cijfers in de uitvoer wanneer de statistische eigenschap gemiddelde of mediaan is." + } + }, + "options": { + "data": { + "entity_ids": "Invoer entiteiten", + "round_digits": "Precisie", + "type": "Statistische karakteristieken" + }, + "description": "Precisie bepaalt het aantal decimale cijfers wanneer het statistische kenmerk gemiddelde of mediaan is." + } + } + }, + "title": "Min / max / gemiddelde / mediaan sensor" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/no.json b/homeassistant/components/min_max/translations/no.json new file mode 100644 index 00000000000..798430cec03 --- /dev/null +++ b/homeassistant/components/min_max/translations/no.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Inndataenheter", + "name": "Navn", + "round_digits": "Presisjon", + "type": "Statistisk karakteristikk" + }, + "data_description": { + "round_digits": "Styrer antall desimaler i utdata n\u00e5r statistikkkarakteristikken er gjennomsnitt eller median." + }, + "description": "Lag en sensor som beregner en min, maks, middelverdi eller medianverdi fra en liste over inngangssensorer.", + "title": "Legg til min / maks / gjennomsnitt / median sensor" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Inndataenheter", + "round_digits": "Presisjon", + "type": "Statistisk karakteristikk" + }, + "data_description": { + "round_digits": "Styrer antall desimaler i utdata n\u00e5r statistikkkarakteristikken er gjennomsnitt eller median." + } + }, + "options": { + "data": { + "entity_ids": "Inndataenheter", + "round_digits": "Presisjon", + "type": "Statistisk karakteristikk" + }, + "description": "Lag en sensor som beregner en min, maks, middelverdi eller medianverdi fra en liste over inngangssensorer." + } + } + }, + "title": "Min / maks / gjennomsnitt / median sensor" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/pl.json b/homeassistant/components/min_max/translations/pl.json new file mode 100644 index 00000000000..0a29ad78339 --- /dev/null +++ b/homeassistant/components/min_max/translations/pl.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Encje wej\u015bciowe", + "name": "Nazwa", + "round_digits": "Precyzja", + "type": "Charakterystyka statystyczna" + }, + "data_description": { + "round_digits": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych, gdy charakterystyka statystyki jest \u015bredni\u0105 lub median\u0105." + }, + "description": "Utw\u00f3rz sensor, kt\u00f3ry oblicza warto\u015b\u0107 minimaln\u0105, maksymaln\u0105, \u015bredni\u0105 lub median\u0119 z listy sensor\u00f3w wej\u015bciowych.", + "title": "Dodawanie sensora min / maks / \u015brednia / mediana" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Encje wej\u015bciowe", + "round_digits": "Precyzja", + "type": "Charakterystyka statystyczna" + }, + "data_description": { + "round_digits": "Kontroluje liczb\u0119 cyfr dziesi\u0119tnych w danych wyj\u015bciowych, gdy charakterystyka statystyki jest \u015bredni\u0105 lub median\u0105." + } + }, + "options": { + "data": { + "entity_ids": "Encje wej\u015bciowe", + "round_digits": "Precyzja", + "type": "Charakterystyka statystyczna" + }, + "description": "Utw\u00f3rz sensor, kt\u00f3ry oblicza warto\u015b\u0107 minimaln\u0105, maksymaln\u0105, \u015bredni\u0105 lub median\u0119 z listy sensor\u00f3w wej\u015bciowych." + } + } + }, + "title": "Sensor min./maks./\u015brednia/mediana" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/pt-BR.json b/homeassistant/components/min_max/translations/pt-BR.json new file mode 100644 index 00000000000..9c1c77c304a --- /dev/null +++ b/homeassistant/components/min_max/translations/pt-BR.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Entidades de entrada", + "name": "Nome", + "round_digits": "Precis\u00e3o", + "type": "Caracter\u00edstica estat\u00edstica" + }, + "data_description": { + "round_digits": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda quando a caracter\u00edstica estat\u00edstica \u00e9 m\u00e9dia ou mediana." + }, + "description": "Crie um sensor que calcula um valor m\u00ednimo, m\u00e1ximo, m\u00e9dio ou mediano de uma lista de sensores de entrada.", + "title": "Adicionar sensor de m\u00ednima / m\u00e1xima / m\u00e9dia / mediana" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Entidades de entrada", + "round_digits": "Precis\u00e3o", + "type": "Caracter\u00edstica estat\u00edstica" + }, + "data_description": { + "round_digits": "Controla o n\u00famero de d\u00edgitos decimais na sa\u00edda quando a caracter\u00edstica estat\u00edstica \u00e9 m\u00e9dia ou mediana." + } + }, + "options": { + "data": { + "entity_ids": "Entidades de entrada", + "round_digits": "Precis\u00e3o", + "type": "Caracter\u00edstica estat\u00edstica" + }, + "description": "Crie um sensor que calcula um valor m\u00ednimo, m\u00e1ximo, m\u00e9dio ou mediano de uma lista de sensores de entrada." + } + } + }, + "title": "Sensor m\u00edn./m\u00e1x./m\u00e9dio/mediano" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/ru.json b/homeassistant/components/min_max/translations/ru.json new file mode 100644 index 00000000000..551660fcabb --- /dev/null +++ b/homeassistant/components/min_max/translations/ru.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "round_digits": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "type": "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0430" + }, + "data_description": { + "round_digits": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0440\u0435\u0434\u043d\u0435\u0439 \u0438\u043b\u0438 \u043c\u0435\u0434\u0438\u0430\u043d\u043d\u043e\u0439." + }, + "description": "\u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435, \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435, \u0441\u0440\u0435\u0434\u043d\u0435\u0435 \u0438\u043b\u0438 \u043c\u0435\u0434\u0438\u0430\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432.", + "title": "\u041c\u0438\u043d\u0438\u043c\u0443\u043c / \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c / \u0441\u0440\u0435\u0434\u043d\u0435\u0435 / \u043c\u0435\u0434\u0438\u0430\u043d\u0430" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", + "round_digits": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "type": "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0430" + }, + "data_description": { + "round_digits": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u044f\u0442\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0440\u0435\u0434\u043d\u0435\u0439 \u0438\u043b\u0438 \u043c\u0435\u0434\u0438\u0430\u043d\u043d\u043e\u0439." + } + }, + "options": { + "data": { + "entity_ids": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b", + "round_digits": "\u041e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435", + "type": "\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0430" + }, + "description": "\u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435, \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435, \u0441\u0440\u0435\u0434\u043d\u0435\u0435 \u0438\u043b\u0438 \u043c\u0435\u0434\u0438\u0430\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432." + } + } + }, + "title": "\u041d\u043e\u0432\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/sv.json b/homeassistant/components/min_max/translations/sv.json new file mode 100644 index 00000000000..d39c277daff --- /dev/null +++ b/homeassistant/components/min_max/translations/sv.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "round_digits": "Precision" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/tr.json b/homeassistant/components/min_max/translations/tr.json new file mode 100644 index 00000000000..7f192888615 --- /dev/null +++ b/homeassistant/components/min_max/translations/tr.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "Giri\u015f varl\u0131klar\u0131", + "name": "Ad", + "round_digits": "Hassas", + "type": "\u0130statistik \u00f6zelli\u011fi" + }, + "data_description": { + "round_digits": "\u0130statistik \u00f6zelli\u011fi ortalama veya medyan oldu\u011funda \u00e7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder." + }, + "description": "Giri\u015f sens\u00f6rleri listesinden minimum, maksimum, ortalama veya medyan de\u011feri hesaplayan bir sens\u00f6r olu\u015fturun.", + "title": "Min / maks / ortalama / medyan sens\u00f6r\u00fc ekle" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "Giri\u015f varl\u0131klar\u0131", + "round_digits": "Hassas", + "type": "\u0130statistik \u00f6zelli\u011fi" + }, + "data_description": { + "round_digits": "\u0130statistik \u00f6zelli\u011fi ortalama veya medyan oldu\u011funda \u00e7\u0131kt\u0131daki ondal\u0131k basamak say\u0131s\u0131n\u0131 kontrol eder." + } + }, + "options": { + "data": { + "entity_ids": "Giri\u015f varl\u0131klar\u0131", + "round_digits": "Hassas", + "type": "\u0130statistik \u00f6zelli\u011fi" + }, + "description": "Giri\u015f sens\u00f6rleri listesinden minimum, maksimum, ortalama veya medyan de\u011feri hesaplayan bir sens\u00f6r olu\u015fturun." + } + } + }, + "title": "Min / maks / ortalama / medyan sens\u00f6r" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/zh-Hans.json b/homeassistant/components/min_max/translations/zh-Hans.json new file mode 100644 index 00000000000..ca5555bbd0a --- /dev/null +++ b/homeassistant/components/min_max/translations/zh-Hans.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\u8f93\u5165\u5b9e\u4f53", + "name": "\u540d\u79f0", + "round_digits": "\u7cbe\u5ea6", + "type": "\u7edf\u8ba1\u9879" + }, + "data_description": { + "round_digits": "\u5f53\u7edf\u8ba1\u9879\u4e3a\u5e73\u5747\u503c\u6216\u4e2d\u4f4d\u6570\u65f6\uff0c\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002" + }, + "description": "\u521b\u5efa\u4f20\u611f\u5668\u6765\u8ba1\u7b97\u591a\u4e2a\u8f93\u5165\u4f20\u611f\u5668\u503c\u7684\u6700\u5927\u503c\u3001\u6700\u5c0f\u503c\u3001\u5e73\u5747\u503c\u3001\u6216\u4e2d\u4f4d\u6570\u3002", + "title": "\u6dfb\u52a0\u6700\u5927\u503c/\u6700\u5c0f\u503c/\u5e73\u5747\u503c/\u4e2d\u4f4d\u6570\u4f20\u611f\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\u8f93\u5165\u5b9e\u4f53", + "round_digits": "\u7cbe\u5ea6", + "type": "\u7edf\u8ba1\u9879" + }, + "data_description": { + "round_digits": "\u5f53\u7edf\u8ba1\u9879\u4e3a\u5e73\u5747\u503c\u6216\u4e2d\u4f4d\u6570\u65f6\uff0c\u63a7\u5236\u8f93\u51fa\u7684\u5c0f\u6570\u4f4d\u6570\u3002" + } + }, + "options": { + "data": { + "entity_ids": "\u8f93\u5165\u5b9e\u4f53", + "round_digits": "\u7cbe\u5ea6", + "type": "\u7edf\u8ba1\u9879" + }, + "description": "\u521b\u5efa\u4f20\u611f\u5668\u6765\u8ba1\u7b97\u591a\u4e2a\u8f93\u5165\u4f20\u611f\u5668\u503c\u7684\u6700\u5927\u503c\u3001\u6700\u5c0f\u503c\u3001\u5e73\u5747\u503c\u3001\u6216\u4e2d\u4f4d\u6570\u3002" + } + } + }, + "title": "\u6700\u5927\u503c/\u6700\u5c0f\u503c/\u5e73\u5747\u503c/\u4e2d\u4f4d\u6570\u4f20\u611f\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/min_max/translations/zh-Hant.json b/homeassistant/components/min_max/translations/zh-Hant.json new file mode 100644 index 00000000000..b0166e26cac --- /dev/null +++ b/homeassistant/components/min_max/translations/zh-Hant.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "entity_ids": "\u8f38\u5165\u5be6\u9ad4", + "name": "\u540d\u7a31", + "round_digits": "\u6e96\u78ba\u5ea6", + "type": "\u7d71\u8a08\u7279\u5fb5" + }, + "data_description": { + "round_digits": "\u7576\u7d71\u8a08\u7279\u5fb5\u70ba\u5e73\u5747\u503c\u6216\u4e2d\u503c\u6642\u3001\u63a7\u5236\u8f38\u51fa\u5c0f\u6578\u4f4d\u6578\u3002" + }, + "description": "\u65b0\u589e\u81ea\u8f38\u5165\u611f\u6e2c\u5668\u4e86\u8868\u4e2d\uff0c\u8a08\u7b97\u6700\u4f4e\u3001\u6700\u9ad8\u3001\u5e73\u5747\u503c\u6216\u4e2d\u503c\u611f\u6e2c\u5668\u3002", + "title": "\u65b0\u589e\u6700\u5c0f\u503c / \u6700\u5927\u503c / \u5e73\u5747\u503c / \u4e2d\u503c\u611f\u6e2c\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "entity_ids": "\u8f38\u5165\u5be6\u9ad4", + "round_digits": "\u6e96\u78ba\u5ea6", + "type": "\u7d71\u8a08\u7279\u5fb5" + }, + "data_description": { + "round_digits": "\u7576\u7d71\u8a08\u7279\u5fb5\u70ba\u5e73\u5747\u503c\u6216\u4e2d\u503c\u6642\u3001\u63a7\u5236\u8f38\u51fa\u5c0f\u6578\u4f4d\u6578\u3002" + } + }, + "options": { + "data": { + "entity_ids": "\u8f38\u5165\u5be6\u9ad4", + "round_digits": "\u6e96\u78ba\u5ea6", + "type": "\u7d71\u8a08\u7279\u5fb5" + }, + "description": "\u65b0\u589e\u81ea\u8f38\u5165\u611f\u6e2c\u5668\u4e86\u8868\u4e2d\uff0c\u8a08\u7b97\u6700\u4f4e\u3001\u6700\u9ad8\u3001\u5e73\u5747\u503c\u6216\u4e2d\u503c\u611f\u6e2c\u5668\u3002" + } + } + }, + "title": "\u6700\u5c0f\u503c / \u6700\u5927\u503c / \u5e73\u5747\u503c / \u4e2d\u503c\u611f\u6e2c\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/hu.json b/homeassistant/components/minecraft_server/translations/hu.json index 02c2a06d8ab..00fff153254 100644 --- a/homeassistant/components/minecraft_server/translations/hu.json +++ b/homeassistant/components/minecraft_server/translations/hu.json @@ -12,7 +12,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "\u00c1ll\u00edtsa be a Minecraft Server p\u00e9ld\u00e1nyt, hogy lehet\u0151v\u00e9 tegye a megfigyel\u00e9st.", "title": "Kapcsold \u00f6ssze a Minecraft szervered" diff --git a/homeassistant/components/minecraft_server/translations/pl.json b/homeassistant/components/minecraft_server/translations/pl.json index fed8d250a90..88b5dd869e4 100644 --- a/homeassistant/components/minecraft_server/translations/pl.json +++ b/homeassistant/components/minecraft_server/translations/pl.json @@ -15,7 +15,7 @@ "name": "Nazwa" }, "description": "Skonfiguruj instancj\u0119 serwera Minecraft, aby umo\u017cliwi\u0107 monitorowanie.", - "title": "Po\u0142\u0105cz sw\u00f3j serwer Minecraft" + "title": "Po\u0142\u0105czenie z Twoim serwerem Minecraft" } } } diff --git a/homeassistant/components/mitemp_bt/sensor.py b/homeassistant/components/mitemp_bt/sensor.py index b3593ebc7e2..e7f6237fdb1 100644 --- a/homeassistant/components/mitemp_bt/sensor.py +++ b/homeassistant/components/mitemp_bt/sensor.py @@ -14,6 +14,7 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.const import ( CONF_FORCE_UPDATE, @@ -57,18 +58,21 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="temperature", name="Temperature", device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=TEMP_CELSIUS, ), SensorEntityDescription( key="humidity", name="Humidity", device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, ), SensorEntityDescription( key="battery", name="Battery", device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, ), ) diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 69588c1b670..91b2271aafa 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -10,13 +10,11 @@ from aiohttp import web import async_timeout import requests from requests.auth import HTTPBasicAuth, HTTPDigestAuth -import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.components.camera import Camera +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_AUTHENTICATION, - CONF_NAME, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL, @@ -24,62 +22,15 @@ from homeassistant.const import ( HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_web, async_get_clientsession, ) from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, DOMAIN, LOGGER -CONTENT_TYPE_HEADER = "Content-Type" - -DEFAULT_NAME = "Mjpeg Camera" -DEFAULT_VERIFY_SSL = True - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_MJPEG_URL): cv.url, - vol.Optional(CONF_STILL_IMAGE_URL): cv.url, - vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In( - [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] - ), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD, default=""): cv.string, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - } -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the MJPEG IP camera from platform.""" - LOGGER.warning( - "Configuration of the MJPEG IP Camera platform in YAML is deprecated " - "and will be removed in Home Assistant 2022.5; Your existing " - "configuration has been imported into the UI automatically and can be " - "safely removed from your configuration.yaml file" - ) - - if discovery_info: - config = PLATFORM_SCHEMA(discovery_info) - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=config, - ) - ) - async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/mjpeg/config_flow.py b/homeassistant/components/mjpeg/config_flow.py index 2eecdd84d78..61c80bcde38 100644 --- a/homeassistant/components/mjpeg/config_flow.py +++ b/homeassistant/components/mjpeg/config_flow.py @@ -174,22 +174,6 @@ class MJPEGFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: - """Handle a flow initialized by importing a config.""" - self._async_abort_entries_match({CONF_MJPEG_URL: config[CONF_MJPEG_URL]}) - return self.async_create_entry( - title=config[CONF_NAME], - data={}, - options={ - CONF_AUTHENTICATION: config[CONF_AUTHENTICATION], - CONF_MJPEG_URL: config[CONF_MJPEG_URL], - CONF_PASSWORD: config[CONF_PASSWORD], - CONF_STILL_IMAGE_URL: config.get(CONF_STILL_IMAGE_URL), - CONF_USERNAME: config.get(CONF_USERNAME), - CONF_VERIFY_SSL: config[CONF_VERIFY_SSL], - }, - ) - class MJPEGOptionsFlowHandler(OptionsFlow): """Handle MJPEG IP Camera options.""" diff --git a/homeassistant/components/mjpeg/translations/fr.json b/homeassistant/components/mjpeg/translations/fr.json index f54c60631a6..405cbc8bf51 100644 --- a/homeassistant/components/mjpeg/translations/fr.json +++ b/homeassistant/components/mjpeg/translations/fr.json @@ -13,6 +13,7 @@ "mjpeg_url": "URL MJPEG", "name": "Nom", "password": "Mot de passe", + "still_image_url": "URL de l'image fixe", "username": "Nom d'utilisateur", "verify_ssl": "V\u00e9rifier le certificat SSL" } @@ -31,6 +32,7 @@ "mjpeg_url": "URL MJPEG", "name": "Nom", "password": "Mot de passe", + "still_image_url": "URL de l'image fixe", "username": "Nom d'utilisateur", "verify_ssl": "V\u00e9rifier le certificat SSL" } diff --git a/homeassistant/components/mjpeg/translations/hu.json b/homeassistant/components/mjpeg/translations/hu.json index 0a87f484887..f3029663306 100644 --- a/homeassistant/components/mjpeg/translations/hu.json +++ b/homeassistant/components/mjpeg/translations/hu.json @@ -11,7 +11,7 @@ "user": { "data": { "mjpeg_url": "MJPEG URL-c\u00edme", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "still_image_url": "\u00c1ll\u00f3k\u00e9p URL-c\u00edme", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", @@ -30,7 +30,7 @@ "init": { "data": { "mjpeg_url": "MJPEG URL-c\u00edme", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "still_image_url": "\u00c1ll\u00f3k\u00e9p URL-c\u00edme", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 860b8ef7b53..f0c7c628976 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -15,7 +15,7 @@ from homeassistant.components import camera, cloud, notify as hass_notify, tag from homeassistant.components.binary_sensor import ( DEVICE_CLASSES as BINARY_SENSOR_CLASSES, ) -from homeassistant.components.camera import SUPPORT_STREAM as CAMERA_SUPPORT_STREAM +from homeassistant.components.camera import CameraEntityFeature from homeassistant.components.device_tracker import ( ATTR_BATTERY, ATTR_GPS, @@ -45,7 +45,7 @@ from homeassistant.helpers import ( template, ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity import validate_entity_category +from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA from homeassistant.util.decorator import Registry from .const import ( @@ -297,7 +297,7 @@ async def webhook_stream_camera(hass, config_entry, data): resp = {"mjpeg_path": f"/api/camera_proxy_stream/{camera_state.entity_id}"} - if camera_state.attributes[ATTR_SUPPORTED_FEATURES] & CAMERA_SUPPORT_STREAM: + if camera_state.attributes[ATTR_SUPPORTED_FEATURES] & CameraEntityFeature.STREAM: try: resp["hls_path"] = await camera.async_request_stream( hass, camera_state.entity_id, "hls" @@ -446,7 +446,7 @@ def _validate_state_class_sensor(value: dict): vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any( None, bool, str, int, float ), - vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, vol.Optional(ATTR_SENSOR_STATE_CLASS): vol.In(SENSOSR_STATE_CLASSES), }, diff --git a/homeassistant/components/mochad/light.py b/homeassistant/components/mochad/light.py index a966d0ad9f6..8cc5f4da0a8 100644 --- a/homeassistant/components/mochad/light.py +++ b/homeassistant/components/mochad/light.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PLATFORM @@ -56,6 +56,9 @@ def setup_platform( class MochadLight(LightEntity): """Representation of a X10 dimmer over Mochad.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, hass, ctrl, dev): """Initialize a Mochad Light Device.""" @@ -89,11 +92,6 @@ class MochadLight(LightEntity): """Return true if the light is on.""" return self._state - @property - def supported_features(self): - """Return supported features.""" - return SUPPORT_BRIGHTNESS - @property def assumed_state(self): """X10 devices are normally 1-way so we have to assume the state.""" diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 3a835f79c55..eb2706c6334 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -6,10 +6,7 @@ import struct from typing import Any from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.const import ( ATTR_TEMPERATURE, CONF_NAME, @@ -63,6 +60,10 @@ async def async_setup_platform( class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): """Representation of a Modbus Thermostat.""" + _attr_hvac_mode = HVACMode.AUTO + _attr_hvac_modes = [HVACMode.AUTO] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__( self, hub: ModbusHub, @@ -73,9 +74,6 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): self._target_temperature_register = config[CONF_TARGET_TEMP] self._unit = config[CONF_TEMPERATURE_UNIT] - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE - self._attr_hvac_mode = HVAC_MODE_AUTO - self._attr_hvac_modes = [HVAC_MODE_AUTO] self._attr_current_temperature = None self._attr_target_temperature = None self._attr_temperature_unit = ( @@ -96,7 +94,7 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): if state and state.attributes.get(ATTR_TEMPERATURE): self._attr_target_temperature = float(state.attributes[ATTR_TEMPERATURE]) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" # Home Assistant expects this method. # We'll keep it here to avoid getting exceptions. diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index a01f732ef13..62344f470c4 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import datetime from typing import Any -from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity +from homeassistant.components.cover import CoverEntity, CoverEntityFeature from homeassistant.const import ( CONF_COVERS, CONF_NAME, @@ -59,6 +59,8 @@ async def async_setup_platform( class ModbusCover(BasePlatform, CoverEntity, RestoreEntity): """Representation of a Modbus cover.""" + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + def __init__( self, hub: ModbusHub, @@ -73,7 +75,6 @@ class ModbusCover(BasePlatform, CoverEntity, RestoreEntity): self._status_register = config.get(CONF_STATUS_REGISTER) self._status_register_type = config[CONF_STATUS_REGISTER_TYPE] - self._attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE self._attr_is_closed = False # If we read cover status from coil, and not from optional status register, diff --git a/homeassistant/components/modbus/light.py b/homeassistant/components/modbus/light.py index 2313dd9bacb..2e5ac62be21 100644 --- a/homeassistant/components/modbus/light.py +++ b/homeassistant/components/modbus/light.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.const import CONF_LIGHTS, CONF_NAME from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -36,6 +36,9 @@ async def async_setup_platform( class ModbusLight(BaseSwitch, LightEntity): """Class representing a Modbus light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + async def async_turn_on(self, **kwargs: Any) -> None: """Set light on.""" await self.async_turn(self.command_on) diff --git a/homeassistant/components/modem_callerid/const.py b/homeassistant/components/modem_callerid/const.py index 0b01c3b761f..d144e2afd5c 100644 --- a/homeassistant/components/modem_callerid/const.py +++ b/homeassistant/components/modem_callerid/const.py @@ -8,7 +8,6 @@ DATA_KEY_API = "api" DEFAULT_NAME = "Phone Modem" DOMAIN = "modem_callerid" ICON = "mdi:phone-classic" -SERVICE_REJECT_CALL = "reject_call" EXCEPTIONS: Final = ( FileNotFoundError, diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index f4b2f3c3e44..e50eabb17aa 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -7,11 +7,11 @@ from phone_modem import PhoneModem from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_DEVICE, EVENT_HOMEASSISTANT_STOP, STATE_IDLE +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_IDLE from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_platform -from .const import CID, DATA_KEY_API, DOMAIN, ICON, SERVICE_REJECT_CALL +from .const import CID, DATA_KEY_API, DOMAIN, ICON _LOGGER = logging.getLogger(__name__) @@ -28,7 +28,6 @@ async def async_setup_entry( ModemCalleridSensor( api, entry.title, - entry.data[CONF_DEVICE], entry.entry_id, ) ] @@ -43,9 +42,6 @@ async def async_setup_entry( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_on_hass_stop) ) - platform = entity_platform.async_get_current_platform() - platform.async_register_entity_service(SERVICE_REJECT_CALL, {}, "async_reject_call") - class ModemCalleridSensor(SensorEntity): """Implementation of USB modem caller ID sensor.""" @@ -53,11 +49,8 @@ class ModemCalleridSensor(SensorEntity): _attr_icon = ICON _attr_should_poll = False - def __init__( - self, api: PhoneModem, name: str, device: str, server_unique_id: str - ) -> None: + def __init__(self, api: PhoneModem, name: str, server_unique_id: str) -> None: """Initialize the sensor.""" - self.device = device self.api = api self._attr_name = name self._attr_unique_id = server_unique_id @@ -85,12 +78,3 @@ class ModemCalleridSensor(SensorEntity): self._attr_extra_state_attributes[CID.CID_TIME] = self.api.cid_time self._attr_native_value = self.api.state self.async_write_ha_state() - - async def async_reject_call(self) -> None: - """Reject Incoming Call.""" - _LOGGER.warning( - "Calling reject_call service is deprecated and will be removed after 2022.4; " - "A new button entity is now available with the same function " - "and replaces the existing service" - ) - await self.api.reject_call(self.device) diff --git a/homeassistant/components/modem_callerid/translations/hu.json b/homeassistant/components/modem_callerid/translations/hu.json index 3a75df30a7d..2a53c24d902 100644 --- a/homeassistant/components/modem_callerid/translations/hu.json +++ b/homeassistant/components/modem_callerid/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "no_devices_found": "Nem tal\u00e1lhat\u00f3 egy\u00e9b eszk\u00f6z" }, "error": { @@ -15,7 +15,7 @@ }, "user": { "data": { - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "port": "Port" }, "description": "Ez egy integr\u00e1ci\u00f3 a CX93001 hangmodemmel t\u00f6rt\u00e9n\u0151 vezet\u00e9kes h\u00edv\u00e1sokhoz. Ez k\u00e9pes lek\u00e9rdezni a h\u00edv\u00f3azonos\u00edt\u00f3 inform\u00e1ci\u00f3t a bej\u00f6v\u0151 h\u00edv\u00e1s visszautas\u00edt\u00e1s\u00e1nak lehet\u0151s\u00e9g\u00e9vel.", diff --git a/homeassistant/components/modern_forms/fan.py b/homeassistant/components/modern_forms/fan.py index 8ebb706096a..318bb8c969d 100644 --- a/homeassistant/components/modern_forms/fan.py +++ b/homeassistant/components/modern_forms/fan.py @@ -6,7 +6,7 @@ from typing import Any from aiomodernforms.const import FAN_POWER_OFF, FAN_POWER_ON import voluptuous as vol -from homeassistant.components.fan import SUPPORT_DIRECTION, SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform @@ -72,6 +72,8 @@ class ModernFormsFanEntity(FanEntity, ModernFormsDeviceEntity): SPEED_RANGE = (1, 6) # off is not included + _attr_supported_features = FanEntityFeature.DIRECTION | FanEntityFeature.SET_SPEED + def __init__( self, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator ) -> None: @@ -83,11 +85,6 @@ class ModernFormsFanEntity(FanEntity, ModernFormsDeviceEntity): ) self._attr_unique_id = f"{self.coordinator.data.info.mac_address}" - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_DIRECTION | SUPPORT_SET_SPEED - @property def percentage(self) -> int | None: """Return the current speed percentage.""" diff --git a/homeassistant/components/modern_forms/light.py b/homeassistant/components/modern_forms/light.py index 7431cf0c3bb..55569054ac4 100644 --- a/homeassistant/components/modern_forms/light.py +++ b/homeassistant/components/modern_forms/light.py @@ -6,11 +6,7 @@ from typing import Any from aiomodernforms.const import LIGHT_POWER_OFF, LIGHT_POWER_ON import voluptuous as vol -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - COLOR_MODE_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform @@ -83,6 +79,9 @@ async def async_setup_entry( class ModernFormsLightEntity(ModernFormsDeviceEntity, LightEntity): """Defines a Modern Forms light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__( self, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator ) -> None: @@ -94,8 +93,6 @@ class ModernFormsLightEntity(ModernFormsDeviceEntity, LightEntity): icon=None, ) self._attr_unique_id = f"{self.coordinator.data.info.mac_address}" - self._attr_color_mode = COLOR_MODE_BRIGHTNESS - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} @property def brightness(self) -> int | None: diff --git a/homeassistant/components/modern_forms/translations/et.json b/homeassistant/components/modern_forms/translations/et.json index ee325440c13..7140888db84 100644 --- a/homeassistant/components/modern_forms/translations/et.json +++ b/homeassistant/components/modern_forms/translations/et.json @@ -19,7 +19,7 @@ "description": "Seadista Modern Forms'i ventilaator sidumiseks Home Assistantiga." }, "zeroconf_confirm": { - "description": "Kas lisada Modern Forms'i ventilaator nimega `{nimi}` Home Assistanti?", + "description": "Kas lisada Modern Forms'i ventilaator nimega `{name}` Home Assistanti?", "title": "Leitud Modern Forms ventilaator" } } diff --git a/homeassistant/components/moehlenhoff_alpha2/climate.py b/homeassistant/components/moehlenhoff_alpha2/climate.py index 783e2df5967..225735293ca 100644 --- a/homeassistant/components/moehlenhoff_alpha2/climate.py +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -3,13 +3,9 @@ import logging from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -43,8 +39,10 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): target_temperature_step = 0.2 - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - _attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_COOL] + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL] _attr_temperature_unit = TEMP_CELSIUS _attr_preset_modes = [PRESET_AUTO, PRESET_DAY, PRESET_NIGHT] @@ -52,6 +50,7 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): """Initialize Alpha2 ClimateEntity.""" super().__init__(coordinator) self.heat_area_id = heat_area_id + self._attr_unique_id = heat_area_id @property def name(self) -> str: @@ -74,24 +73,24 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): return float(self.coordinator.data[self.heat_area_id].get("T_ACTUAL", 0.0)) @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return current hvac mode.""" if self.coordinator.get_cooling(): - return HVAC_MODE_COOL - return HVAC_MODE_HEAT + return HVACMode.COOL + return HVACMode.HEAT - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - await self.coordinator.async_set_cooling(hvac_mode == HVAC_MODE_COOL) + await self.coordinator.async_set_cooling(hvac_mode == HVACMode.COOL) @property - def hvac_action(self) -> str: + def hvac_action(self) -> HVACAction: """Return the current running hvac operation.""" if not self.coordinator.data[self.heat_area_id]["_HEATCTRL_STATE"]: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if self.coordinator.get_cooling(): - return CURRENT_HVAC_COOL - return CURRENT_HVAC_HEAT + return HVACAction.COOLING + return HVACAction.HEATING @property def target_temperature(self) -> float: diff --git a/homeassistant/components/monoprice/media_player.py b/homeassistant/components/monoprice/media_player.py index eea674a07ee..627d95427e2 100644 --- a/homeassistant/components/monoprice/media_player.py +++ b/homeassistant/components/monoprice/media_player.py @@ -4,14 +4,9 @@ import logging from serial import SerialException from homeassistant import core -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PORT, STATE_OFF, STATE_ON @@ -33,15 +28,6 @@ _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 1 -SUPPORT_MONOPRICE = ( - SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE -) - @core.callback def _get_sources_from_dict(data): @@ -127,6 +113,15 @@ async def async_setup_entry( class MonopriceZone(MediaPlayerEntity): """Representation of a Monoprice amplifier zone.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, monoprice, sources, namespace, zone_id): """Initialize new zone.""" self._monoprice = monoprice @@ -211,11 +206,6 @@ class MonopriceZone(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._mute - @property - def supported_features(self): - """Return flag of media commands that are supported.""" - return SUPPORT_MONOPRICE - @property def media_title(self): """Return the current source as medial title.""" diff --git a/homeassistant/components/moon/translations/cs.json b/homeassistant/components/moon/translations/cs.json new file mode 100644 index 00000000000..f1c26a20f6e --- /dev/null +++ b/homeassistant/components/moon/translations/cs.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + }, + "step": { + "user": { + "description": "Chcete za\u010d\u00edt nastavovat?" + } + } + }, + "title": "M\u011bs\u00edc" +} \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.fi.json b/homeassistant/components/moon/translations/sensor.fi.json index f7788972044..a35afac10d6 100644 --- a/homeassistant/components/moon/translations/sensor.fi.json +++ b/homeassistant/components/moon/translations/sensor.fi.json @@ -4,7 +4,11 @@ "first_quarter": "Ensimm\u00e4inen nelj\u00e4nnes", "full_moon": "T\u00e4ysikuu", "last_quarter": "Viimeinen nelj\u00e4nnes", - "new_moon": "Uusikuu" + "new_moon": "Uusikuu", + "waning_crescent": "V\u00e4henev\u00e4 sirppi", + "waning_gibbous": "V\u00e4henev\u00e4 kuperakuu", + "waxing_crescent": "Kasvava sirppi", + "waxing_gibbous": "Kasvava kuperakuu" } } } \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index 508851e6dad..63c80d33dba 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -113,9 +113,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.async_on_unload(entry.add_update_listener(update_listener)) + # check multicast interface + check_multicast_class = ConnectMotionGateway(hass, interface=multicast_interface) + working_interface = await check_multicast_class.async_check_interface(host, key) + if working_interface != multicast_interface: + data = {**entry.data, CONF_INTERFACE: working_interface} + hass.config_entries.async_update_entry(entry, data=data) + _LOGGER.debug( + "Motion Blinds interface updated from %s to %s, " + "this should only occur after a network change", + multicast_interface, + working_interface, + ) + # Create multicast Listener if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]: - multicast = AsyncMotionMulticast(interface=multicast_interface) + multicast = AsyncMotionMulticast(interface=working_interface) hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast # start listening for local pushes (only once) await multicast.Start_listen() diff --git a/homeassistant/components/motion_blinds/config_flow.py b/homeassistant/components/motion_blinds/config_flow.py index a956289d72e..e40f22296cb 100644 --- a/homeassistant/components/motion_blinds/config_flow.py +++ b/homeassistant/components/motion_blinds/config_flow.py @@ -1,11 +1,9 @@ """Config flow to configure Motion Blinds using their WLAN API.""" -from socket import gaierror - -from motionblinds import AsyncMotionMulticast, MotionDiscovery +from motionblinds import MotionDiscovery import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import dhcp, network +from homeassistant.components import dhcp from homeassistant.const import CONF_API_KEY, CONF_HOST from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult @@ -131,27 +129,20 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: key = user_input[CONF_API_KEY] - multicast_interface = user_input[CONF_INTERFACE] - # check socket interface - if multicast_interface != DEFAULT_INTERFACE: - motion_multicast = AsyncMotionMulticast(interface=multicast_interface) - try: - await motion_multicast.Start_listen() - motion_multicast.Stop_listen() - except gaierror: - errors[CONF_INTERFACE] = "invalid_interface" - return self.async_show_form( - step_id="connect", - data_schema=self._config_settings, - errors=errors, - ) - - connect_gateway_class = ConnectMotionGateway(self.hass, multicast=None) + connect_gateway_class = ConnectMotionGateway(self.hass) if not await connect_gateway_class.async_connect_gateway(self._host, key): return self.async_abort(reason="connection_error") motion_gateway = connect_gateway_class.gateway_device + # check socket interface + check_multicast_class = ConnectMotionGateway( + self.hass, interface=DEFAULT_INTERFACE + ) + multicast_interface = await check_multicast_class.async_check_interface( + self._host, key + ) + mac_address = motion_gateway.mac await self.async_set_unique_id(mac_address, raise_on_progress=False) @@ -172,38 +163,12 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): }, ) - (interfaces, default_interface) = await self.async_get_interfaces() - self._config_settings = vol.Schema( { vol.Required(CONF_API_KEY): vol.All(str, vol.Length(min=16, max=16)), - vol.Optional(CONF_INTERFACE, default=default_interface): vol.In( - interfaces - ), } ) return self.async_show_form( step_id="connect", data_schema=self._config_settings, errors=errors ) - - async def async_get_interfaces(self): - """Get list of interface to use.""" - interfaces = [DEFAULT_INTERFACE, "0.0.0.0"] - enabled_interfaces = [] - default_interface = DEFAULT_INTERFACE - - adapters = await network.async_get_adapters(self.hass) - for adapter in adapters: - if ipv4s := adapter["ipv4"]: - ip4 = ipv4s[0]["address"] - interfaces.append(ip4) - if adapter["enabled"]: - enabled_interfaces.append(ip4) - if adapter["default"]: - default_interface = ip4 - - if len(enabled_interfaces) == 1: - default_interface = enabled_interfaces[0] - - return (interfaces, default_interface) diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index 115a0e42e01..ccd4043faee 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -58,6 +58,7 @@ TILT_DEVICE_MAP = { BlindType.VenetianBlind: CoverDeviceClass.BLIND, BlindType.ShangriLaBlind: CoverDeviceClass.BLIND, BlindType.DoubleRoller: CoverDeviceClass.SHADE, + BlindType.DualShade: CoverDeviceClass.SHADE, BlindType.VerticalBlind: CoverDeviceClass.BLIND, BlindType.VerticalBlindLeft: CoverDeviceClass.BLIND, BlindType.VerticalBlindRight: CoverDeviceClass.BLIND, diff --git a/homeassistant/components/motion_blinds/gateway.py b/homeassistant/components/motion_blinds/gateway.py index 6f8032e5a65..1775f7deb7d 100644 --- a/homeassistant/components/motion_blinds/gateway.py +++ b/homeassistant/components/motion_blinds/gateway.py @@ -1,8 +1,13 @@ """Code to handle a Motion Gateway.""" +import contextlib import logging import socket -from motionblinds import MotionGateway +from motionblinds import AsyncMotionMulticast, MotionGateway + +from homeassistant.components import network + +from .const import DEFAULT_INTERFACE _LOGGER = logging.getLogger(__name__) @@ -10,11 +15,12 @@ _LOGGER = logging.getLogger(__name__) class ConnectMotionGateway: """Class to async connect to a Motion Gateway.""" - def __init__(self, hass, multicast): + def __init__(self, hass, multicast=None, interface=None): """Initialize the entity.""" self._hass = hass self._multicast = multicast self._gateway_device = None + self._interface = interface @property def gateway_device(self): @@ -48,3 +54,78 @@ class ConnectMotionGateway: self.gateway_device.protocol, ) return True + + def check_interface(self): + """Check if the current interface supports multicast.""" + with contextlib.suppress(socket.timeout): + return self.gateway_device.Check_gateway_multicast() + return False + + async def async_get_interfaces(self): + """Get list of interface to use.""" + interfaces = [DEFAULT_INTERFACE, "0.0.0.0"] + enabled_interfaces = [] + default_interface = DEFAULT_INTERFACE + + adapters = await network.async_get_adapters(self._hass) + for adapter in adapters: + if ipv4s := adapter["ipv4"]: + ip4 = ipv4s[0]["address"] + interfaces.append(ip4) + if adapter["enabled"]: + enabled_interfaces.append(ip4) + if adapter["default"]: + default_interface = ip4 + + if len(enabled_interfaces) == 1: + default_interface = enabled_interfaces[0] + interfaces.remove(default_interface) + interfaces.insert(0, default_interface) + + if self._interface is not None: + interfaces.remove(self._interface) + interfaces.insert(0, self._interface) + + return interfaces + + async def async_check_interface(self, host, key): + """Connect to the Motion Gateway.""" + interfaces = await self.async_get_interfaces() + for interface in interfaces: + _LOGGER.debug( + "Checking Motion Blinds interface '%s' with host %s", interface, host + ) + # initialize multicast listener + check_multicast = AsyncMotionMulticast(interface=interface) + try: + await check_multicast.Start_listen() + except socket.gaierror: + continue + + # trigger test multicast + self._gateway_device = MotionGateway( + ip=host, key=key, multicast=check_multicast + ) + result = await self._hass.async_add_executor_job(self.check_interface) + + # close multicast listener again + try: + check_multicast.Stop_listen() + except socket.gaierror: + continue + + if result: + # successfully received multicast + _LOGGER.debug( + "Success using Motion Blinds interface '%s' with host %s", + interface, + host, + ) + return interface + + _LOGGER.error( + "Could not find working interface for Motion Blinds host %s, using interface '%s'", + host, + self._interface, + ) + return self._interface diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index 4f4575ae6dd..54aa5c97659 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,12 +3,18 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.6.2"], + "requirements": ["motionblinds==0.6.5"], "dependencies": ["network"], "dhcp": [ { "registered_devices": true }, { "hostname": "motion_*" + }, + { + "hostname": "brel_*" + }, + { + "hostname": "connector_*" } ], "codeowners": ["@starkillerOG"], diff --git a/homeassistant/components/motion_blinds/strings.json b/homeassistant/components/motion_blinds/strings.json index c62c6dc2873..13a4d117344 100644 --- a/homeassistant/components/motion_blinds/strings.json +++ b/homeassistant/components/motion_blinds/strings.json @@ -11,8 +11,7 @@ "connect": { "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions", "data": { - "api_key": "[%key:common::config_flow::data::api_key%]", - "interface": "The network interface to use" + "api_key": "[%key:common::config_flow::data::api_key%]" } }, "select": { @@ -24,8 +23,7 @@ } }, "error": { - "discovery_error": "Failed to discover a Motion Gateway", - "invalid_interface": "Invalid network interface" + "discovery_error": "Failed to discover a Motion Gateway" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%], connection settings are updated", diff --git a/homeassistant/components/motion_blinds/translations/ca.json b/homeassistant/components/motion_blinds/translations/ca.json index 1db1976cbd3..18cc8f892d6 100644 --- a/homeassistant/components/motion_blinds/translations/ca.json +++ b/homeassistant/components/motion_blinds/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_configured": "El dispositiu ja est\u00e0 configurat, la configuraci\u00f3 de connexi\u00f3 s'ha actualitzat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "connection_error": "Ha fallat la connexi\u00f3" }, @@ -9,7 +9,7 @@ "discovery_error": "No s'ha pogut descobrir cap Motion Gateway", "invalid_interface": "Interf\u00edcie de xarxa no v\u00e0lida" }, - "flow_title": "Motion Blinds", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/de.json b/homeassistant/components/motion_blinds/translations/de.json index a4758c85d4b..c5ced3ddd55 100644 --- a/homeassistant/components/motion_blinds/translations/de.json +++ b/homeassistant/components/motion_blinds/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert, Verbindungseinstellungen werden aktualisiert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "connection_error": "Verbindung fehlgeschlagen" }, @@ -9,7 +9,7 @@ "discovery_error": "Motion-Gateway konnte nicht gefunden werden", "invalid_interface": "Ung\u00fcltige Netzwerkschnittstelle" }, - "flow_title": "Jalousien", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/et.json b/homeassistant/components/motion_blinds/translations/et.json index 7091c402a2e..935e345ea3a 100644 --- a/homeassistant/components/motion_blinds/translations/et.json +++ b/homeassistant/components/motion_blinds/translations/et.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "already_configured": "Seade on juba h\u00e4\u00e4lestatud, \u00fchenduse s\u00e4tted on v\u00e4rskendatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "connection_error": "\u00dchendamine nurjus" }, @@ -9,7 +9,7 @@ "discovery_error": "Motion Gateway avastamine nurjus", "invalid_interface": "Sobimatu v\u00f5rguliides" }, - "flow_title": "", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/fr.json b/homeassistant/components/motion_blinds/translations/fr.json index 75fb1daa9be..09cf93fde60 100644 --- a/homeassistant/components/motion_blinds/translations/fr.json +++ b/homeassistant/components/motion_blinds/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9, les param\u00e8tres de connexion ont \u00e9t\u00e9 mis \u00e0 jour", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "connection_error": "\u00c9chec de connexion" }, @@ -9,7 +9,7 @@ "discovery_error": "Impossible de d\u00e9couvrir une Motion Gateway", "invalid_interface": "Interface r\u00e9seau non valide" }, - "flow_title": "Stores de mouvement", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/he.json b/homeassistant/components/motion_blinds/translations/he.json index 0876de6504a..ce6f3d2918e 100644 --- a/homeassistant/components/motion_blinds/translations/he.json +++ b/homeassistant/components/motion_blinds/translations/he.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4 \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d4\u05d7\u05d9\u05d1\u05d5\u05e8 \u05e2\u05d5\u05d3\u05db\u05e0\u05d5", "already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea", "connection_error": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4" }, diff --git a/homeassistant/components/motion_blinds/translations/hu.json b/homeassistant/components/motion_blinds/translations/hu.json index 9d0f41db143..64334c54a28 100644 --- a/homeassistant/components/motion_blinds/translations/hu.json +++ b/homeassistant/components/motion_blinds/translations/hu.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van, a csatlakoz\u00e1si be\u00e1ll\u00edt\u00e1sai friss\u00edtve vannak", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "connection_error": "Sikertelen csatlakoz\u00e1s" }, "error": { "discovery_error": "Nem siker\u00fclt felfedezni a Motion Gateway-t", "invalid_interface": "\u00c9rv\u00e9nytelen h\u00e1l\u00f3zati interf\u00e9sz" }, - "flow_title": "Mozg\u00f3 red\u0151ny", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { @@ -17,7 +17,7 @@ "interface": "A haszn\u00e1lni k\u00edv\u00e1nt h\u00e1l\u00f3zati interf\u00e9sz" }, "description": "Sz\u00fcks\u00e9ge lesz a 16 karakteres API kulcsra, \u00fatmutat\u00e1s\u00e9rt l\u00e1sd: https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", - "title": "Mozg\u00f3 red\u0151ny" + "title": "Red\u0151ny/rol\u00f3" }, "select": { "data": { @@ -32,7 +32,7 @@ "host": "IP c\u00edm" }, "description": "Csatlakozzon a Motion Gateway-hez, ha az IP-c\u00edm nincs be\u00e1ll\u00edtva, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja", - "title": "Mozg\u00f3 red\u0151ny" + "title": "Red\u0151ny/rol\u00f3" } } }, @@ -43,7 +43,7 @@ "wait_for_push": "Multicast adatokra v\u00e1rakoz\u00e1s friss\u00edt\u00e9skor" }, "description": "Opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1sa", - "title": "Motion Blinds" + "title": "Red\u0151ny/rol\u00f3" } } } diff --git a/homeassistant/components/motion_blinds/translations/id.json b/homeassistant/components/motion_blinds/translations/id.json index 269470110ec..d576ced8c0a 100644 --- a/homeassistant/components/motion_blinds/translations/id.json +++ b/homeassistant/components/motion_blinds/translations/id.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Perangkat sudah dikonfigurasi", + "already_configured": "Perangkat sudah dikonfigurasi, pengaturan koneksi diperbarui", "already_in_progress": "Alur konfigurasi sedang berlangsung", "connection_error": "Gagal terhubung" }, @@ -9,7 +9,7 @@ "discovery_error": "Gagal menemukan Motion Gateway", "invalid_interface": "Antarmuka jaringan tidak valid" }, - "flow_title": "Motion Blinds", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/it.json b/homeassistant/components/motion_blinds/translations/it.json index 6a9cef8a0af..0871ce07bf8 100644 --- a/homeassistant/components/motion_blinds/translations/it.json +++ b/homeassistant/components/motion_blinds/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato, le impostazioni di connessione sono aggiornate", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "connection_error": "Impossibile connettersi" }, @@ -9,7 +9,7 @@ "discovery_error": "Impossibile rilevare un Motion Gateway", "invalid_interface": "Interfaccia di rete non valida" }, - "flow_title": "Tende Motion", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/nl.json b/homeassistant/components/motion_blinds/translations/nl.json index 3d3d078b814..6cb69b06b87 100644 --- a/homeassistant/components/motion_blinds/translations/nl.json +++ b/homeassistant/components/motion_blinds/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd, verbindingsinstellingen zijn bijgewerkt", "already_in_progress": "De configuratiestroom is al aan de gang", "connection_error": "Kan geen verbinding maken" }, @@ -9,7 +9,7 @@ "discovery_error": "Kan geen Motion Gateway vinden", "invalid_interface": "Ongeldige netwerkinterface" }, - "flow_title": "Motion Blinds", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/no.json b/homeassistant/components/motion_blinds/translations/no.json index 242a6647da9..d6c4708ea8c 100644 --- a/homeassistant/components/motion_blinds/translations/no.json +++ b/homeassistant/components/motion_blinds/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert", + "already_configured": "Enheten er allerede konfigurert , tilkoblingsinnstillingene er oppdatert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "connection_error": "Tilkobling mislyktes" }, @@ -9,7 +9,7 @@ "discovery_error": "Kunne ikke oppdage en Motion Gateway", "invalid_interface": "Ugyldig nettverksgrensesnitt" }, - "flow_title": "Motion Blinds", + "flow_title": "{short_mac} ( {ip_address} )", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/pl.json b/homeassistant/components/motion_blinds/translations/pl.json index 6161b6aa3da..2a042859b88 100644 --- a/homeassistant/components/motion_blinds/translations/pl.json +++ b/homeassistant/components/motion_blinds/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane, ustawienia po\u0142\u0105czenia zosta\u0142y zaktualizowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, @@ -9,7 +9,7 @@ "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 bramki ruchu", "invalid_interface": "Nieprawid\u0142owy interfejs sieciowy" }, - "flow_title": "Rolety Motion", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/pt-BR.json b/homeassistant/components/motion_blinds/translations/pt-BR.json index dcabdbd16e5..01658cea852 100644 --- a/homeassistant/components/motion_blinds/translations/pt-BR.json +++ b/homeassistant/components/motion_blinds/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado, as configura\u00e7\u00f5es de conex\u00e3o s\u00e3o atualizadas", "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", "connection_error": "Falha ao conectar" }, @@ -9,7 +9,7 @@ "discovery_error": "Falha ao descobrir um Motion Gateway", "invalid_interface": "Interface de rede inv\u00e1lida" }, - "flow_title": "Cortinas de movimento", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/ru.json b/homeassistant/components/motion_blinds/translations/ru.json index 83fe77ff9eb..e7a4492bd54 100644 --- a/homeassistant/components/motion_blinds/translations/ru.json +++ b/homeassistant/components/motion_blinds/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant. \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b.", "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, @@ -9,7 +9,7 @@ "discovery_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0448\u043b\u044e\u0437 Motion.", "invalid_interface": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441." }, - "flow_title": "Motion Blinds", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/tr.json b/homeassistant/components/motion_blinds/translations/tr.json index c9a0efe2538..824569ce173 100644 --- a/homeassistant/components/motion_blinds/translations/tr.json +++ b/homeassistant/components/motion_blinds/translations/tr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f , ba\u011flant\u0131 ayarlar\u0131 g\u00fcncellendi", "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "connection_error": "Ba\u011flanma hatas\u0131" }, @@ -9,7 +9,7 @@ "discovery_error": "Motion Gateway bulunamad\u0131", "invalid_interface": "Ge\u00e7ersiz a\u011f aray\u00fcz\u00fc" }, - "flow_title": "Hareketli Panjurlar", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/motion_blinds/translations/zh-Hant.json b/homeassistant/components/motion_blinds/translations/zh-Hant.json index df300e0511f..e7fa565ba36 100644 --- a/homeassistant/components/motion_blinds/translations/zh-Hant.json +++ b/homeassistant/components/motion_blinds/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u9023\u7dda\u8a2d\u5b9a\u5df2\u66f4\u65b0", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "connection_error": "\u9023\u7dda\u5931\u6557" }, @@ -9,7 +9,7 @@ "discovery_error": "\u641c\u7d22 Motion \u9598\u9053\u5668\u5931\u6557", "invalid_interface": "\u7db2\u8def\u4ecb\u9762\u7121\u6548" }, - "flow_title": "Motion Blinds", + "flow_title": "{short_mac} ({ip_address})", "step": { "connect": { "data": { diff --git a/homeassistant/components/mpd/manifest.json b/homeassistant/components/mpd/manifest.json index 9bfeb8dad03..404feabeaf0 100644 --- a/homeassistant/components/mpd/manifest.json +++ b/homeassistant/components/mpd/manifest.json @@ -3,7 +3,7 @@ "name": "Music Player Daemon (MPD)", "documentation": "https://www.home-assistant.io/integrations/mpd", "requirements": ["python-mpd2==3.0.5"], - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "local_polling", "loggers": ["mpd"] } diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 509584a867f..d3262a0d5da 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -12,7 +12,11 @@ from mpd.asyncio import MPDClient import voluptuous as vol from homeassistant.components import media_source -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) @@ -22,23 +26,6 @@ from homeassistant.components.media_player.const import ( REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( CONF_HOST, @@ -64,19 +51,19 @@ DEFAULT_PORT = 6600 PLAYLIST_UPDATE_INTERVAL = timedelta(seconds=120) SUPPORT_MPD = ( - SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_REPEAT_SET - | SUPPORT_SHUFFLE_SET - | SUPPORT_SEEK - | SUPPORT_STOP - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_BROWSE_MEDIA + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.BROWSE_MEDIA ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -373,9 +360,13 @@ class MpdDevice(MediaPlayerEntity): supported = SUPPORT_MPD if "volume" in self._status: - supported |= SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE + supported |= ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + ) if self._playlists is not None: - supported |= SUPPORT_SELECT_SOURCE + supported |= MediaPlayerEntityFeature.SELECT_SOURCE return supported diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index cca4e58658c..a58e71e78d6 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -8,14 +8,7 @@ import re import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_CUSTOM_BYPASS, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_ARM_VACATION, - SUPPORT_ALARM_TRIGGER, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_CODE, @@ -228,12 +221,12 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): def supported_features(self) -> int: """Return the list of supported features.""" return ( - SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_ARM_VACATION - | SUPPORT_ALARM_ARM_CUSTOM_BYPASS - | SUPPORT_ALARM_TRIGGER + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.ARM_VACATION + | AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS + | AlarmControlPanelEntityFeature.TRIGGER ) @property @@ -242,8 +235,8 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): if (code := self._config.get(CONF_CODE)) is None: return None if code == REMOTE_CODE or (isinstance(code, str) and re.search("^\\d+$", code)): - return alarm.FORMAT_NUMBER - return alarm.FORMAT_TEXT + return alarm.CodeFormat.NUMBER + return alarm.CodeFormat.TEXT @property def code_arm_required(self): diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 94320cc5def..5c53380bb71 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -10,6 +10,7 @@ from homeassistant.components import climate from homeassistant.components.climate import ( PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, ClimateEntity, + ClimateEntityFeature, ) from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, @@ -30,12 +31,6 @@ from homeassistant.components.climate.const import ( HVAC_MODE_OFF, PRESET_AWAY, PRESET_NONE, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -1040,27 +1035,27 @@ class MqttClimate(MqttEntity, ClimateEntity): if (self._topic[CONF_TEMP_STATE_TOPIC] is not None) or ( self._topic[CONF_TEMP_COMMAND_TOPIC] is not None ): - support |= SUPPORT_TARGET_TEMPERATURE + support |= ClimateEntityFeature.TARGET_TEMPERATURE if (self._topic[CONF_TEMP_LOW_STATE_TOPIC] is not None) or ( self._topic[CONF_TEMP_LOW_COMMAND_TOPIC] is not None ): - support |= SUPPORT_TARGET_TEMPERATURE_RANGE + support |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE if (self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is not None) or ( self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None ): - support |= SUPPORT_TARGET_TEMPERATURE_RANGE + support |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or ( self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None ): - support |= SUPPORT_FAN_MODE + support |= ClimateEntityFeature.FAN_MODE if (self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None) or ( self._topic[CONF_SWING_MODE_COMMAND_TOPIC] is not None ): - support |= SUPPORT_SWING_MODE + support |= ClimateEntityFeature.SWING_MODE # AWAY and HOLD mode topics and templates are deprecated, support will be removed with release 2022.9 if ( @@ -1070,12 +1065,12 @@ class MqttClimate(MqttEntity, ClimateEntity): or (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or (self._topic[CONF_HOLD_COMMAND_TOPIC] is not None) ): - support |= SUPPORT_PRESET_MODE + support |= ClimateEntityFeature.PRESET_MODE if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or ( self._topic[CONF_AUX_COMMAND_TOPIC] is not None ): - support |= SUPPORT_AUX_HEAT + support |= ClimateEntityFeature.AUX_HEAT return support diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 282e57fea9e..76f984f1bc9 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -12,15 +12,8 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DEVICE_CLASSES_SCHEMA, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -102,10 +95,10 @@ DEFAULT_TILT_OPEN_POSITION = 100 DEFAULT_TILT_OPTIMISTIC = False TILT_FEATURES = ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_STOP_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION ) MQTT_COVER_ATTRIBUTES_BLOCKED = frozenset( @@ -519,14 +512,14 @@ class MqttCover(MqttEntity, CoverEntity): supported_features = 0 if self._config.get(CONF_COMMAND_TOPIC) is not None: if self._config.get(CONF_PAYLOAD_OPEN) is not None: - supported_features |= SUPPORT_OPEN + supported_features |= CoverEntityFeature.OPEN if self._config.get(CONF_PAYLOAD_CLOSE) is not None: - supported_features |= SUPPORT_CLOSE + supported_features |= CoverEntityFeature.CLOSE if self._config.get(CONF_PAYLOAD_STOP) is not None: - supported_features |= SUPPORT_STOP + supported_features |= CoverEntityFeature.STOP if self._config.get(CONF_SET_POSITION_TOPIC) is not None: - supported_features |= SUPPORT_SET_POSITION + supported_features |= CoverEntityFeature.SET_POSITION if self._config.get(CONF_TILT_COMMAND_TOPIC) is not None: supported_features |= TILT_FEATURES diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 71c0a9f9364..56cfc3efc6b 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Callable import logging -from typing import Any +from typing import Any, cast import attr import voluptuous as vol @@ -13,6 +13,7 @@ from homeassistant.components.automation import ( AutomationTriggerInfo, ) from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DEVICE, CONF_DEVICE_ID, @@ -23,30 +24,19 @@ from homeassistant.const import ( ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, device_registry as dr -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType from . import debug_info, trigger as mqtt_trigger from .. import mqtt -from .const import ( - ATTR_DISCOVERY_HASH, - ATTR_DISCOVERY_TOPIC, - CONF_PAYLOAD, - CONF_QOS, - CONF_TOPIC, - DOMAIN, -) -from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_UPDATED, clear_discovery_hash +from .const import ATTR_DISCOVERY_HASH, CONF_PAYLOAD, CONF_QOS, CONF_TOPIC, DOMAIN +from .discovery import MQTT_DISCOVERY_DONE from .mixins import ( - CONF_CONNECTIONS, - CONF_IDENTIFIERS, MQTT_ENTITY_DEVICE_INFO_SCHEMA, - cleanup_device_registry, - device_info_from_config, + MqttDiscoveryDeviceUpdate, + send_discovery_done, + update_device, ) _LOGGER = logging.getLogger(__name__) @@ -89,6 +79,8 @@ TRIGGER_DISCOVERY_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( DEVICE_TRIGGERS = "mqtt_device_triggers" +LOG_NAME = "Device trigger" + @attr.s(slots=True) class TriggerInstance: @@ -99,7 +91,7 @@ class TriggerInstance: trigger: Trigger = attr.ib() remove: CALLBACK_TYPE | None = attr.ib(default=None) - async def async_attach_trigger(self): + async def async_attach_trigger(self) -> None: """Attach MQTT trigger.""" mqtt_config = { mqtt_trigger.CONF_PLATFORM: mqtt.DOMAIN, @@ -132,14 +124,15 @@ class Trigger: hass: HomeAssistant = attr.ib() payload: str | None = attr.ib() qos: int | None = attr.ib() - remove_signal: Callable[[], None] | None = attr.ib() subtype: str = attr.ib() topic: str | None = attr.ib() type: str = attr.ib() value_template: str | None = attr.ib() trigger_instances: list[TriggerInstance] = attr.ib(factory=list) - async def add_trigger(self, action, automation_info): + async def add_trigger( + self, action: AutomationActionType, automation_info: AutomationTriggerInfo + ) -> Callable: """Add MQTT trigger.""" instance = TriggerInstance(action, automation_info, self) self.trigger_instances.append(instance) @@ -160,9 +153,8 @@ class Trigger: return async_remove - async def update_trigger(self, config, discovery_hash, remove_signal): + async def update_trigger(self, config: ConfigType) -> None: """Update MQTT device trigger.""" - self.remove_signal = remove_signal self.type = config[CONF_TYPE] self.subtype = config[CONF_SUBTYPE] self.payload = config[CONF_PAYLOAD] @@ -178,7 +170,7 @@ class Trigger: for trig in self.trigger_instances: await trig.async_attach_trigger() - def detach_trigger(self): + def detach_trigger(self) -> None: """Remove MQTT device trigger.""" # Mark trigger as unknown self.topic = None @@ -190,110 +182,110 @@ class Trigger: trig.remove = None -def _update_device(hass, config_entry, config): - """Update device registry.""" - device_registry = dr.async_get(hass) - config_entry_id = config_entry.entry_id - device_info = device_info_from_config(config[CONF_DEVICE]) +class MqttDeviceTrigger(MqttDiscoveryDeviceUpdate): + """Setup a MQTT device trigger with auto discovery.""" - if config_entry_id is not None and device_info is not None: - device_info["config_entry_id"] = config_entry_id - device_registry.async_get_or_create(**device_info) + def __init__( + self, + hass: HomeAssistant, + config: ConfigType, + device_id: str, + discovery_data: dict, + config_entry: ConfigEntry, + ) -> None: + """Initialize.""" + self._config = config + self._config_entry = config_entry + self.device_id = device_id + self.discovery_data = discovery_data + self.hass = hass + + MqttDiscoveryDeviceUpdate.__init__( + self, + hass, + discovery_data, + device_id, + config_entry, + LOG_NAME, + ) + + async def async_setup(self) -> None: + """Initialize the device trigger.""" + discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] + discovery_id = discovery_hash[1] + if discovery_id not in self.hass.data.setdefault(DEVICE_TRIGGERS, {}): + self.hass.data[DEVICE_TRIGGERS][discovery_id] = Trigger( + hass=self.hass, + device_id=self.device_id, + discovery_data=self.discovery_data, + type=self._config[CONF_TYPE], + subtype=self._config[CONF_SUBTYPE], + topic=self._config[CONF_TOPIC], + payload=self._config[CONF_PAYLOAD], + qos=self._config[CONF_QOS], + value_template=self._config[CONF_VALUE_TEMPLATE], + ) + else: + await self.hass.data[DEVICE_TRIGGERS][discovery_id].update_trigger( + self._config + ) + debug_info.add_trigger_discovery_data( + self.hass, discovery_hash, self.discovery_data, self.device_id + ) + + async def async_update(self, discovery_data: dict) -> None: + """Handle MQTT device trigger discovery updates.""" + discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] + discovery_id = discovery_hash[1] + debug_info.update_trigger_discovery_data( + self.hass, discovery_hash, discovery_data + ) + config = TRIGGER_DISCOVERY_SCHEMA(discovery_data) + update_device(self.hass, self._config_entry, config) + device_trigger: Trigger = self.hass.data[DEVICE_TRIGGERS][discovery_id] + await device_trigger.update_trigger(config) + + async def async_tear_down(self) -> None: + """Cleanup device trigger.""" + discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] + discovery_id = discovery_hash[1] + if discovery_id in self.hass.data[DEVICE_TRIGGERS]: + _LOGGER.info("Removing trigger: %s", discovery_hash) + trigger: Trigger = self.hass.data[DEVICE_TRIGGERS][discovery_id] + trigger.detach_trigger() + debug_info.remove_trigger_discovery_data(self.hass, discovery_hash) -async def async_setup_trigger(hass, config, config_entry, discovery_data): +async def async_setup_trigger( + hass, config: ConfigType, config_entry: ConfigEntry, discovery_data: dict +) -> None: """Set up the MQTT device trigger.""" config = TRIGGER_DISCOVERY_SCHEMA(config) discovery_hash = discovery_data[ATTR_DISCOVERY_HASH] - discovery_id = discovery_hash[1] - remove_signal = None - async def discovery_update(payload): - """Handle discovery update.""" - _LOGGER.info( - "Got update for trigger with hash: %s '%s'", discovery_hash, payload - ) - if not payload: - # Empty payload: Remove trigger - _LOGGER.info("Removing trigger: %s", discovery_hash) - debug_info.remove_trigger_discovery_data(hass, discovery_hash) - if discovery_id in hass.data[DEVICE_TRIGGERS]: - device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id] - device_trigger.detach_trigger() - clear_discovery_hash(hass, discovery_hash) - remove_signal() - await cleanup_device_registry(hass, device.id, config_entry.entry_id) - else: - # Non-empty payload: Update trigger - _LOGGER.info("Updating trigger: %s", discovery_hash) - debug_info.update_trigger_discovery_data(hass, discovery_hash, payload) - config = TRIGGER_DISCOVERY_SCHEMA(payload) - _update_device(hass, config_entry, config) - device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id] - await device_trigger.update_trigger(config, discovery_hash, remove_signal) - async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None) - - remove_signal = async_dispatcher_connect( - hass, MQTT_DISCOVERY_UPDATED.format(discovery_hash), discovery_update - ) - - _update_device(hass, config_entry, config) - - device_registry = dr.async_get(hass) - device = device_registry.async_get_device( - {(DOMAIN, id_) for id_ in config[CONF_DEVICE][CONF_IDENTIFIERS]}, - {tuple(x) for x in config[CONF_DEVICE][CONF_CONNECTIONS]}, - ) - - if device is None: + if (device_id := update_device(hass, config_entry, config)) is None: async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None) return - if DEVICE_TRIGGERS not in hass.data: - hass.data[DEVICE_TRIGGERS] = {} - if discovery_id not in hass.data[DEVICE_TRIGGERS]: - hass.data[DEVICE_TRIGGERS][discovery_id] = Trigger( - hass=hass, - device_id=device.id, - discovery_data=discovery_data, - type=config[CONF_TYPE], - subtype=config[CONF_SUBTYPE], - topic=config[CONF_TOPIC], - payload=config[CONF_PAYLOAD], - qos=config[CONF_QOS], - remove_signal=remove_signal, - value_template=config[CONF_VALUE_TEMPLATE], - ) - else: - await hass.data[DEVICE_TRIGGERS][discovery_id].update_trigger( - config, discovery_hash, remove_signal - ) - debug_info.add_trigger_discovery_data( - hass, discovery_hash, discovery_data, device.id + mqtt_device_trigger = MqttDeviceTrigger( + hass, config, device_id, discovery_data, config_entry ) - - async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None) + await mqtt_device_trigger.async_setup() + send_discovery_done(hass, discovery_data) -async def async_removed_from_device(hass: HomeAssistant, device_id: str): +async def async_removed_from_device(hass: HomeAssistant, device_id: str) -> None: """Handle Mqtt removed from a device.""" triggers = await async_get_triggers(hass, device_id) for trig in triggers: - device_trigger = hass.data[DEVICE_TRIGGERS].pop(trig[CONF_DISCOVERY_ID]) + device_trigger: Trigger = hass.data[DEVICE_TRIGGERS].pop( + trig[CONF_DISCOVERY_ID] + ) if device_trigger: - discovery_hash = device_trigger.discovery_data[ATTR_DISCOVERY_HASH] - discovery_topic = device_trigger.discovery_data[ATTR_DISCOVERY_TOPIC] - - debug_info.remove_trigger_discovery_data(hass, discovery_hash) device_trigger.detach_trigger() - clear_discovery_hash(hass, discovery_hash) - device_trigger.remove_signal() - mqtt.publish( - hass, - discovery_topic, - "", - retain=True, - ) + discovery_data = cast(dict, device_trigger.discovery_data) + discovery_hash = discovery_data[ATTR_DISCOVERY_HASH] + debug_info.remove_trigger_discovery_data(hass, discovery_hash) async def async_get_triggers( @@ -328,8 +320,7 @@ async def async_attach_trigger( automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Attach a trigger.""" - if DEVICE_TRIGGERS not in hass.data: - hass.data[DEVICE_TRIGGERS] = {} + hass.data.setdefault(DEVICE_TRIGGERS, {}) device_id = config[CONF_DEVICE_ID] discovery_id = config[CONF_DISCOVERY_ID] @@ -338,7 +329,6 @@ async def async_attach_trigger( hass=hass, device_id=device_id, discovery_data=None, - remove_signal=None, type=config[CONF_TYPE], subtype=config[CONF_SUBTYPE], topic=None, diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 11bc0f6839a..fae443dc411 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -1,4 +1,6 @@ """Support for MQTT discovery.""" +from __future__ import annotations + import asyncio from collections import deque import functools @@ -73,20 +75,22 @@ LAST_DISCOVERY = "mqtt_last_discovery" TOPIC_BASE = "~" -def clear_discovery_hash(hass, discovery_hash): +class MQTTConfig(dict): + """Dummy class to allow adding attributes.""" + + discovery_data: dict + + +def clear_discovery_hash(hass: HomeAssistant, discovery_hash: tuple) -> None: """Clear entry in ALREADY_DISCOVERED list.""" del hass.data[ALREADY_DISCOVERED][discovery_hash] -def set_discovery_hash(hass, discovery_hash): +def set_discovery_hash(hass: HomeAssistant, discovery_hash: tuple): """Clear entry in ALREADY_DISCOVERED list.""" hass.data[ALREADY_DISCOVERED][discovery_hash] = {} -class MQTTConfig(dict): - """Dummy class to allow adding attributes.""" - - async def async_start( # noqa: C901 hass: HomeAssistant, discovery_topic, config_entry=None ) -> None: @@ -181,6 +185,7 @@ async def async_start( # noqa: C901 await async_process_discovery_payload(component, discovery_id, payload) async def async_process_discovery_payload(component, discovery_id, payload): + """Process the payload of a new discovery.""" _LOGGER.debug("Process discovery payload %s", payload) discovery_hash = (component, discovery_id) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 89ea7dbdd4b..ff2eab7a68f 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -12,10 +12,8 @@ from homeassistant.components.fan import ( ATTR_OSCILLATING, ATTR_PERCENTAGE, ATTR_PRESET_MODE, - SUPPORT_OSCILLATE, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -326,12 +324,12 @@ class MqttFan(MqttEntity, FanEntity): self._supported_features = 0 self._supported_features |= ( self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None - and SUPPORT_OSCILLATE + and FanEntityFeature.OSCILLATE ) if self._feature_percentage: - self._supported_features |= SUPPORT_SET_SPEED + self._supported_features |= FanEntityFeature.SET_SPEED if self._feature_preset_mode: - self._supported_features |= SUPPORT_PRESET_MODE + self._supported_features |= FanEntityFeature.PRESET_MODE for key, tpl in self._command_templates.items(): self._command_templates[key] = MqttCommandTemplate( diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 99674051521..f5a8b372532 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -12,9 +12,9 @@ from homeassistant.components.humidifier import ( ATTR_MODE, DEFAULT_MAX_HUMIDITY, DEFAULT_MIN_HUMIDITY, - SUPPORT_MODES, HumidifierDeviceClass, HumidifierEntity, + HumidifierEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -250,7 +250,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): else: self._available_modes = [] if self._available_modes: - self._attr_supported_features = SUPPORT_MODES + self._attr_supported_features = HumidifierEntityFeature.MODES else: self._attr_supported_features = 0 diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 09b23029b0e..b56c06a43e0 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -19,22 +19,13 @@ from homeassistant.components.light import ( ATTR_WHITE, ATTR_WHITE_VALUE, ATTR_XY_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_UNKNOWN, - COLOR_MODE_WHITE, - COLOR_MODE_XY, ENTITY_ID_FORMAT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, + ColorMode, LightEntity, valid_supported_color_modes, ) @@ -384,35 +375,35 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): self._optimistic_xy_color = optimistic or topic[CONF_XY_STATE_TOPIC] is None supported_color_modes = set() if topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_COLOR_TEMP) - self._color_mode = COLOR_MODE_COLOR_TEMP + supported_color_modes.add(ColorMode.COLOR_TEMP) + self._color_mode = ColorMode.COLOR_TEMP if topic[CONF_HS_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_HS) - self._color_mode = COLOR_MODE_HS + supported_color_modes.add(ColorMode.HS) + self._color_mode = ColorMode.HS if topic[CONF_RGB_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_RGB) - self._color_mode = COLOR_MODE_RGB + supported_color_modes.add(ColorMode.RGB) + self._color_mode = ColorMode.RGB if topic[CONF_RGBW_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_RGBW) - self._color_mode = COLOR_MODE_RGBW + supported_color_modes.add(ColorMode.RGBW) + self._color_mode = ColorMode.RGBW if topic[CONF_RGBWW_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_RGBWW) - self._color_mode = COLOR_MODE_RGBWW + supported_color_modes.add(ColorMode.RGBWW) + self._color_mode = ColorMode.RGBWW if topic[CONF_WHITE_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_WHITE) + supported_color_modes.add(ColorMode.WHITE) if topic[CONF_XY_COMMAND_TOPIC] is not None: - supported_color_modes.add(COLOR_MODE_XY) - self._color_mode = COLOR_MODE_XY + supported_color_modes.add(ColorMode.XY) + self._color_mode = ColorMode.XY if len(supported_color_modes) > 1: - self._color_mode = COLOR_MODE_UNKNOWN + self._color_mode = ColorMode.UNKNOWN if not supported_color_modes: if topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None: - self._color_mode = COLOR_MODE_BRIGHTNESS - supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._color_mode = ColorMode.BRIGHTNESS + supported_color_modes.add(ColorMode.BRIGHTNESS) else: - self._color_mode = COLOR_MODE_ONOFF - supported_color_modes.add(COLOR_MODE_ONOFF) + self._color_mode = ColorMode.ONOFF + supported_color_modes.add(ColorMode.ONOFF) # Validate the color_modes configuration self._supported_color_modes = valid_supported_color_modes(supported_color_modes) @@ -503,7 +494,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): def rgb_received(msg): """Handle new MQTT messages for RGB.""" rgb = _rgbx_received( - msg, CONF_RGB_VALUE_TEMPLATE, COLOR_MODE_RGB, lambda *x: x + msg, CONF_RGB_VALUE_TEMPLATE, ColorMode.RGB, lambda *x: x ) if not rgb: return @@ -522,7 +513,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): rgbw = _rgbx_received( msg, CONF_RGBW_VALUE_TEMPLATE, - COLOR_MODE_RGBW, + ColorMode.RGBW, color_util.color_rgbw_to_rgb, ) if not rgbw: @@ -539,7 +530,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): rgbww = _rgbx_received( msg, CONF_RGBWW_VALUE_TEMPLATE, - COLOR_MODE_RGBWW, + ColorMode.RGBWW, color_util.color_rgbww_to_rgb, ) if not rgbww: @@ -577,7 +568,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): return if self._optimistic_color_mode: - self._color_mode = COLOR_MODE_COLOR_TEMP + self._color_mode = ColorMode.COLOR_TEMP self._color_temp = int(payload) self.async_write_ha_state() @@ -610,7 +601,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): try: hs_color = tuple(float(val) for val in payload.split(",", 2)) if self._optimistic_color_mode: - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS self._hs_color = hs_color self.async_write_ha_state() except ValueError: @@ -647,7 +638,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): xy_color = tuple(float(val) for val in payload.split(",")) if self._optimistic_color_mode: - self._color_mode = COLOR_MODE_XY + self._color_mode = ColorMode.XY if self._legacy_mode: self._hs_color = color_util.color_xy_to_hs(*xy_color) else: @@ -863,9 +854,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): """Render RGBx payload.""" if tpl := self._command_templates[template]: keys = ["red", "green", "blue"] - if color_mode == COLOR_MODE_RGBW: + if color_mode == ColorMode.RGBW: keys.append("white") - elif color_mode == COLOR_MODE_RGBWW: + elif color_mode == ColorMode.RGBWW: keys.extend(["cold_white", "warm_white"]) rgb_color_str = tpl(variables=zip(keys, color)) else: @@ -905,7 +896,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): ): # Legacy mode: Convert HS to RGB rgb = scale_rgbx(color_util.color_hsv_to_RGB(*hs_color, 100)) - rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, COLOR_MODE_RGB) + rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, ColorMode.RGB) await publish(CONF_RGB_COMMAND_TOPIC, rgb_s) should_update |= set_optimistic( ATTR_HS_COLOR, hs_color, condition_attribute=ATTR_RGB_COLOR @@ -913,7 +904,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): if hs_color and self._topic[CONF_HS_COMMAND_TOPIC] is not None: await publish(CONF_HS_COMMAND_TOPIC, f"{hs_color[0]},{hs_color[1]}") - should_update |= set_optimistic(ATTR_HS_COLOR, hs_color, COLOR_MODE_HS) + should_update |= set_optimistic(ATTR_HS_COLOR, hs_color, ColorMode.HS) if ( hs_color @@ -933,9 +924,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and not self._legacy_mode ): scaled = scale_rgbx(rgb) - rgb_s = render_rgbx(scaled, CONF_RGB_COMMAND_TEMPLATE, COLOR_MODE_RGB) + rgb_s = render_rgbx(scaled, CONF_RGB_COMMAND_TEMPLATE, ColorMode.RGB) await publish(CONF_RGB_COMMAND_TOPIC, rgb_s) - should_update |= set_optimistic(ATTR_RGB_COLOR, rgb, COLOR_MODE_RGB) + should_update |= set_optimistic(ATTR_RGB_COLOR, rgb, ColorMode.RGB) if ( (rgbw := kwargs.get(ATTR_RGBW_COLOR)) @@ -943,9 +934,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and not self._legacy_mode ): scaled = scale_rgbx(rgbw) - rgbw_s = render_rgbx(scaled, CONF_RGBW_COMMAND_TEMPLATE, COLOR_MODE_RGBW) + rgbw_s = render_rgbx(scaled, CONF_RGBW_COMMAND_TEMPLATE, ColorMode.RGBW) await publish(CONF_RGBW_COMMAND_TOPIC, rgbw_s) - should_update |= set_optimistic(ATTR_RGBW_COLOR, rgbw, COLOR_MODE_RGBW) + should_update |= set_optimistic(ATTR_RGBW_COLOR, rgbw, ColorMode.RGBW) if ( (rgbww := kwargs.get(ATTR_RGBWW_COLOR)) @@ -953,9 +944,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and not self._legacy_mode ): scaled = scale_rgbx(rgbww) - rgbww_s = render_rgbx(scaled, CONF_RGBWW_COMMAND_TEMPLATE, COLOR_MODE_RGBWW) + rgbww_s = render_rgbx(scaled, CONF_RGBWW_COMMAND_TEMPLATE, ColorMode.RGBWW) await publish(CONF_RGBWW_COMMAND_TOPIC, rgbww_s) - should_update |= set_optimistic(ATTR_RGBWW_COLOR, rgbww, COLOR_MODE_RGBWW) + should_update |= set_optimistic(ATTR_RGBWW_COLOR, rgbww, ColorMode.RGBWW) if ( (xy_color := kwargs.get(ATTR_XY_COLOR)) @@ -963,7 +954,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and not self._legacy_mode ): await publish(CONF_XY_COMMAND_TOPIC, f"{xy_color[0]},{xy_color[1]}") - should_update |= set_optimistic(ATTR_XY_COLOR, xy_color, COLOR_MODE_XY) + should_update |= set_optimistic(ATTR_XY_COLOR, xy_color, ColorMode.XY) if ( ATTR_BRIGHTNESS in kwargs @@ -990,7 +981,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): hs_color = self._hs_color if self._hs_color is not None else (0, 0) brightness = kwargs[ATTR_BRIGHTNESS] rgb = scale_rgbx(color_util.color_hsv_to_RGB(*hs_color, 100), brightness) - rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, COLOR_MODE_RGB) + rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, ColorMode.RGB) await publish(CONF_RGB_COMMAND_TOPIC, rgb_s) should_update |= set_optimistic(ATTR_BRIGHTNESS, kwargs[ATTR_BRIGHTNESS]) elif ( @@ -1001,7 +992,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): ): rgb_color = self._rgb_color if self._rgb_color is not None else (255,) * 3 rgb = scale_rgbx(rgb_color, kwargs[ATTR_BRIGHTNESS]) - rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, COLOR_MODE_RGB) + rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, ColorMode.RGB) await publish(CONF_RGB_COMMAND_TOPIC, rgb_s) should_update |= set_optimistic(ATTR_BRIGHTNESS, kwargs[ATTR_BRIGHTNESS]) elif ( @@ -1014,7 +1005,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): self._rgbw_color if self._rgbw_color is not None else (255,) * 4 ) rgbw = scale_rgbx(rgbw_color, kwargs[ATTR_BRIGHTNESS]) - rgbw_s = render_rgbx(rgbw, CONF_RGBW_COMMAND_TEMPLATE, COLOR_MODE_RGBW) + rgbw_s = render_rgbx(rgbw, CONF_RGBW_COMMAND_TEMPLATE, ColorMode.RGBW) await publish(CONF_RGBW_COMMAND_TOPIC, rgbw_s) should_update |= set_optimistic(ATTR_BRIGHTNESS, kwargs[ATTR_BRIGHTNESS]) elif ( @@ -1027,7 +1018,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): self._rgbww_color if self._rgbww_color is not None else (255,) * 5 ) rgbww = scale_rgbx(rgbww_color, kwargs[ATTR_BRIGHTNESS]) - rgbww_s = render_rgbx(rgbww, CONF_RGBWW_COMMAND_TEMPLATE, COLOR_MODE_RGBWW) + rgbww_s = render_rgbx(rgbww, CONF_RGBWW_COMMAND_TEMPLATE, ColorMode.RGBWW) await publish(CONF_RGBWW_COMMAND_TOPIC, rgbww_s) should_update |= set_optimistic(ATTR_BRIGHTNESS, kwargs[ATTR_BRIGHTNESS]) if ( @@ -1040,7 +1031,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): await publish(CONF_COLOR_TEMP_COMMAND_TOPIC, color_temp) should_update |= set_optimistic( - ATTR_COLOR_TEMP, kwargs[ATTR_COLOR_TEMP], COLOR_MODE_COLOR_TEMP + ATTR_COLOR_TEMP, kwargs[ATTR_COLOR_TEMP], ColorMode.COLOR_TEMP ) if ATTR_EFFECT in kwargs and self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None: @@ -1059,7 +1050,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): should_update |= set_optimistic( ATTR_BRIGHTNESS, kwargs[ATTR_WHITE], - COLOR_MODE_WHITE, + ColorMode.WHITE, ) if ( diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 83d40ed5aae..68cc7e8b36c 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -18,12 +18,6 @@ from homeassistant.components.light import ( ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - COLOR_MODE_XY, ENTITY_ID_FORMAT, FLASH_LONG, FLASH_SHORT, @@ -35,6 +29,7 @@ from homeassistant.components.light import ( SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, VALID_COLOR_MODES, + ColorMode, LightEntity, legacy_supported_features, valid_supported_color_modes, @@ -267,39 +262,39 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): _LOGGER.warning("Invalid color mode received") return try: - if color_mode == COLOR_MODE_COLOR_TEMP: + if color_mode == ColorMode.COLOR_TEMP: self._color_temp = int(values["color_temp"]) - self._color_mode = COLOR_MODE_COLOR_TEMP - elif color_mode == COLOR_MODE_HS: + self._color_mode = ColorMode.COLOR_TEMP + elif color_mode == ColorMode.HS: hue = float(values["color"]["h"]) saturation = float(values["color"]["s"]) - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS self._hs = (hue, saturation) - elif color_mode == COLOR_MODE_RGB: + elif color_mode == ColorMode.RGB: r = int(values["color"]["r"]) # pylint: disable=invalid-name g = int(values["color"]["g"]) # pylint: disable=invalid-name b = int(values["color"]["b"]) # pylint: disable=invalid-name - self._color_mode = COLOR_MODE_RGB + self._color_mode = ColorMode.RGB self._rgb = (r, g, b) - elif color_mode == COLOR_MODE_RGBW: + elif color_mode == ColorMode.RGBW: r = int(values["color"]["r"]) # pylint: disable=invalid-name g = int(values["color"]["g"]) # pylint: disable=invalid-name b = int(values["color"]["b"]) # pylint: disable=invalid-name w = int(values["color"]["w"]) # pylint: disable=invalid-name - self._color_mode = COLOR_MODE_RGBW + self._color_mode = ColorMode.RGBW self._rgbw = (r, g, b, w) - elif color_mode == COLOR_MODE_RGBWW: + elif color_mode == ColorMode.RGBWW: r = int(values["color"]["r"]) # pylint: disable=invalid-name g = int(values["color"]["g"]) # pylint: disable=invalid-name b = int(values["color"]["b"]) # pylint: disable=invalid-name c = int(values["color"]["c"]) # pylint: disable=invalid-name w = int(values["color"]["w"]) # pylint: disable=invalid-name - self._color_mode = COLOR_MODE_RGBWW + self._color_mode = ColorMode.RGBWW self._rgbww = (r, g, b, c, w) - elif color_mode == COLOR_MODE_XY: + elif color_mode == ColorMode.XY: x = float(values["color"]["x"]) # pylint: disable=invalid-name y = float(values["color"]["y"]) # pylint: disable=invalid-name - self._color_mode = COLOR_MODE_XY + self._color_mode = ColorMode.XY self._xy = (x, y) except (KeyError, ValueError): _LOGGER.warning("Invalid or incomplete color value received") @@ -553,31 +548,31 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self._hs = kwargs[ATTR_HS_COLOR] should_update = True - if ATTR_HS_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_HS): + if ATTR_HS_COLOR in kwargs and self._supports_color_mode(ColorMode.HS): hs_color = kwargs[ATTR_HS_COLOR] message["color"] = {"h": hs_color[0], "s": hs_color[1]} if self._optimistic: - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS self._hs = hs_color should_update = True - if ATTR_RGB_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_RGB): + if ATTR_RGB_COLOR in kwargs and self._supports_color_mode(ColorMode.RGB): rgb = self._scale_rgbxx(kwargs[ATTR_RGB_COLOR], kwargs) message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2]} if self._optimistic: - self._color_mode = COLOR_MODE_RGB + self._color_mode = ColorMode.RGB self._rgb = rgb should_update = True - if ATTR_RGBW_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_RGBW): + if ATTR_RGBW_COLOR in kwargs and self._supports_color_mode(ColorMode.RGBW): rgb = self._scale_rgbxx(kwargs[ATTR_RGBW_COLOR], kwargs) message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2], "w": rgb[3]} if self._optimistic: - self._color_mode = COLOR_MODE_RGBW + self._color_mode = ColorMode.RGBW self._rgbw = rgb should_update = True - if ATTR_RGBWW_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_RGBWW): + if ATTR_RGBWW_COLOR in kwargs and self._supports_color_mode(ColorMode.RGBWW): rgb = self._scale_rgbxx(kwargs[ATTR_RGBWW_COLOR], kwargs) message["color"] = { "r": rgb[0], @@ -587,15 +582,15 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): "w": rgb[4], } if self._optimistic: - self._color_mode = COLOR_MODE_RGBWW + self._color_mode = ColorMode.RGBWW self._rgbww = rgb should_update = True - if ATTR_XY_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_XY): + if ATTR_XY_COLOR in kwargs and self._supports_color_mode(ColorMode.XY): xy = kwargs[ATTR_XY_COLOR] # pylint: disable=invalid-name message["color"] = {"x": xy[0], "y": xy[1]} if self._optimistic: - self._color_mode = COLOR_MODE_XY + self._color_mode = ColorMode.XY self._xy = xy should_update = True diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 1dd73175b3b..66efe9fece7 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -6,7 +6,7 @@ import functools import voluptuous as vol from homeassistant.components import lock -from homeassistant.components.lock import SUPPORT_OPEN, LockEntity +from homeassistant.components.lock import LockEntity, LockEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE from homeassistant.core import HomeAssistant, callback @@ -176,7 +176,7 @@ class MqttLock(MqttEntity, LockEntity): @property def supported_features(self): """Flag supported features.""" - return SUPPORT_OPEN if CONF_PAYLOAD_OPEN in self._config else 0 + return LockEntityFeature.OPEN if CONF_PAYLOAD_OPEN in self._config else 0 async def async_lock(self, **kwargs): """Lock the device. diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 09efb384196..43f75f08459 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -5,7 +5,7 @@ from abc import abstractmethod from collections.abc import Callable import json import logging -from typing import Any, Protocol, final +from typing import Any, Protocol, cast, final import voluptuous as vol @@ -32,20 +32,21 @@ from homeassistant.helpers import ( device_registry as dr, entity_registry as er, ) +from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity import ( + ENTITY_CATEGORIES_SCHEMA, DeviceInfo, Entity, EntityCategory, async_generate_entity_id, - validate_entity_category, ) from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.reload import async_setup_reload_service -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import ( DATA_MQTT, @@ -212,7 +213,7 @@ MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend( { vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_ENABLED_BY_DEFAULT, default=True): cv.boolean, - vol.Optional(CONF_ENTITY_CATEGORY): validate_entity_category, + vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic, vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template, @@ -496,8 +497,10 @@ class MqttAvailability(Entity): return self._available_latest -async def cleanup_device_registry(hass, device_id, config_entry_id): - """Remove device registry entry if there are no remaining entities or triggers.""" +async def cleanup_device_registry( + hass: HomeAssistant, device_id: str | None, config_entry_id: str | None +) -> None: + """Remove MQTT from the device registry entry if there are no remaining entities, triggers or tags.""" # Local import to avoid circular dependencies # pylint: disable-next=import-outside-toplevel from . import device_trigger, tag @@ -506,6 +509,7 @@ async def cleanup_device_registry(hass, device_id, config_entry_id): entity_registry = er.async_get(hass) if ( device_id + and config_entry_id and not er.async_entries_for_device( entity_registry, device_id, include_disabled_entities=False ) @@ -517,14 +521,163 @@ async def cleanup_device_registry(hass, device_id, config_entry_id): ) +def get_discovery_hash(discovery_data: dict) -> tuple: + """Get the discovery hash from the discovery data.""" + return discovery_data[ATTR_DISCOVERY_HASH] + + +def send_discovery_done(hass: HomeAssistant, discovery_data: dict) -> None: + """Acknowledge a discovery message has been handled.""" + discovery_hash = get_discovery_hash(discovery_data) + async_dispatcher_send(hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None) + + +def stop_discovery_updates( + hass: HomeAssistant, + discovery_data: dict, + remove_discovery_updated: Callable[[], None] | None = None, +) -> None: + """Stop discovery updates of being sent.""" + if remove_discovery_updated: + remove_discovery_updated() + remove_discovery_updated = None + discovery_hash = get_discovery_hash(discovery_data) + clear_discovery_hash(hass, discovery_hash) + + +async def async_remove_discovery_payload(hass: HomeAssistant, discovery_data: dict): + """Clear retained discovery topic in broker to avoid rediscovery after a restart of HA.""" + discovery_topic = discovery_data[ATTR_DISCOVERY_TOPIC] + await async_publish(hass, discovery_topic, "", retain=True) + + +class MqttDiscoveryDeviceUpdate: + """Add support for auto discovery for platforms without an entity.""" + + def __init__( + self, + hass: HomeAssistant, + discovery_data: dict, + device_id: str | None, + config_entry: ConfigEntry, + log_name: str, + ) -> None: + """Initialize the update service.""" + + self.hass = hass + self.log_name = log_name + + self._discovery_data = discovery_data + self._device_id = device_id + self._config_entry = config_entry + self._config_entry_id = config_entry.entry_id + self._skip_device_removal: bool = False + + discovery_hash = get_discovery_hash(discovery_data) + self._remove_discovery_updated = async_dispatcher_connect( + hass, + MQTT_DISCOVERY_UPDATED.format(discovery_hash), + self.async_discovery_update, + ) + if device_id is not None: + self._remove_device_updated = hass.bus.async_listen( + EVENT_DEVICE_REGISTRY_UPDATED, self._async_device_removed + ) + _LOGGER.info( + "%s %s has been initialized", + self.log_name, + discovery_hash, + ) + + async def async_discovery_update( + self, + discovery_payload: DiscoveryInfoType | None, + ) -> None: + """Handle discovery update.""" + discovery_hash = get_discovery_hash(self._discovery_data) + _LOGGER.info( + "Got update for %s with hash: %s '%s'", + self.log_name, + discovery_hash, + discovery_payload, + ) + if ( + discovery_payload + and discovery_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD] + ): + _LOGGER.info( + "%s %s updating", + self.log_name, + discovery_hash, + ) + await self.async_update(discovery_payload) + if not discovery_payload: + # Unregister and clean up the current discovery instance + stop_discovery_updates( + self.hass, self._discovery_data, self._remove_discovery_updated + ) + await self._async_tear_down() + send_discovery_done(self.hass, self._discovery_data) + _LOGGER.info( + "%s %s has been removed", + self.log_name, + discovery_hash, + ) + else: + # Normal update without change + send_discovery_done(self.hass, self._discovery_data) + _LOGGER.info( + "%s %s no changes", + self.log_name, + discovery_hash, + ) + return + + async def _async_device_removed(self, event: Event) -> None: + """Handle the manual removal of a device.""" + if self._skip_device_removal or not async_removed_from_device( + self.hass, event, cast(str, self._device_id), self._config_entry_id + ): + return + # Prevent a second cleanup round after the device is removed + self._remove_device_updated() + self._skip_device_removal = True + # Unregister and clean up and publish an empty payload + # so the service is not rediscovered after a restart + stop_discovery_updates( + self.hass, self._discovery_data, self._remove_discovery_updated + ) + await self._async_tear_down() + await async_remove_discovery_payload(self.hass, self._discovery_data) + + async def _async_tear_down(self) -> None: + """Handle the cleanup of the discovery service.""" + # Cleanup platform resources + await self.async_tear_down() + # remove the service for auto discovery updates and clean up the device registry + if not self._skip_device_removal: + # Prevent a second cleanup round after the device is removed + self._skip_device_removal = True + await cleanup_device_registry( + self.hass, self._device_id, self._config_entry_id + ) + + async def async_update(self, discovery_data: dict) -> None: + """Handle the update of platform specific parts, extend to the platform.""" + + @abstractmethod + async def async_tear_down(self) -> None: + """Handle the cleanup of platform specific parts, extend to the platform.""" + + class MqttDiscoveryUpdate(Entity): - """Mixin used to handle updated discovery message.""" + """Mixin used to handle updated discovery message for entity based platforms.""" def __init__(self, discovery_data, discovery_update=None) -> None: """Initialize the discovery update mixin.""" self._discovery_data = discovery_data self._discovery_update = discovery_update - self._remove_signal: Callable | None = None + self._remove_discovery_updated: Callable | None = None self._removed_from_hass = False async def async_added_to_hass(self) -> None: @@ -572,7 +725,7 @@ class MqttDiscoveryUpdate(Entity): else: # Non-empty, unchanged payload: Ignore to avoid changing states _LOGGER.info("Ignoring unchanged update for: %s", self.entity_id) - self.async_send_discovery_done() + send_discovery_done(self.hass, self._discovery_data) if discovery_hash: debug_info.add_entity_discovery_data( @@ -580,24 +733,12 @@ class MqttDiscoveryUpdate(Entity): ) # Set in case the entity has been removed and is re-added, for example when changing entity_id set_discovery_hash(self.hass, discovery_hash) - self._remove_signal = async_dispatcher_connect( + self._remove_discovery_updated = async_dispatcher_connect( self.hass, MQTT_DISCOVERY_UPDATED.format(discovery_hash), discovery_callback, ) - @callback - def async_send_discovery_done(self) -> None: - """Acknowledge a discovery message has been handled.""" - discovery_hash = ( - self._discovery_data[ATTR_DISCOVERY_HASH] if self._discovery_data else None - ) - if not discovery_hash: - return - async_dispatcher_send( - self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None - ) - async def async_removed_from_registry(self) -> None: """Clear retained discovery topic in broker.""" if not self._removed_from_hass: @@ -606,18 +747,14 @@ class MqttDiscoveryUpdate(Entity): self._cleanup_discovery_on_remove() # Clear the discovery topic so the entity is not rediscovered after a restart - discovery_topic = self._discovery_data[ATTR_DISCOVERY_TOPIC] - await async_publish(self.hass, discovery_topic, "", retain=True) + await async_remove_discovery_payload(self.hass, self._discovery_data) @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" if self._discovery_data: - discovery_hash = self._discovery_data[ATTR_DISCOVERY_HASH] - clear_discovery_hash(self.hass, discovery_hash) - async_dispatcher_send( - self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None - ) + stop_discovery_updates(self.hass, self._discovery_data) + send_discovery_done(self.hass, self._discovery_data) super().add_to_platform_abort() async def async_will_remove_from_hass(self) -> None: @@ -627,13 +764,11 @@ class MqttDiscoveryUpdate(Entity): def _cleanup_discovery_on_remove(self) -> None: """Stop listening to signal and cleanup discovery data.""" if self._discovery_data and not self._removed_from_hass: - clear_discovery_hash(self.hass, self._discovery_data[ATTR_DISCOVERY_HASH]) + stop_discovery_updates( + self.hass, self._discovery_data, self._remove_discovery_updated + ) self._removed_from_hass = True - if self._remove_signal: - self._remove_signal() - self._remove_signal = None - def device_info_from_config(config) -> DeviceInfo | None: """Return a device description for device registry.""" @@ -737,7 +872,8 @@ class MqttEntity( self._prepare_subscribe_topics() await self._subscribe_topics() await self.mqtt_async_added_to_hass() - self.async_send_discovery_done() + if self._discovery_data is not None: + send_discovery_done(self.hass, self._discovery_data) async def mqtt_async_added_to_hass(self): """Call before the discovery message is acknowledged. @@ -839,6 +975,28 @@ class MqttEntity( return self._unique_id +def update_device( + hass: HomeAssistant, + config_entry: ConfigEntry, + config: ConfigType, +) -> str | None: + """Update device registry.""" + if CONF_DEVICE not in config: + return None + + device = None + device_registry = dr.async_get(hass) + config_entry_id = config_entry.entry_id + device_info = device_info_from_config(config[CONF_DEVICE]) + + if config_entry_id is not None and device_info is not None: + update_device_info = cast(dict, device_info) + update_device_info["config_entry_id"] = config_entry_id + device = device_registry.async_get_or_create(**update_device_info) + + return device.id if device else None + + @callback def async_removed_from_device( hass: HomeAssistant, event: Event, mqtt_device_id: str, config_entry_id: str diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index f5a0270481e..9cec65d7254 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -1,4 +1,4 @@ -"""Modesl used by multiple MQTT modules.""" +"""Models used by multiple MQTT modules.""" from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 8619bc799a4..beed5b51e20 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -13,6 +13,7 @@ from homeassistant.components import siren from homeassistant.components.siren import ( TURN_ON_SCHEMA, SirenEntity, + SirenEntityFeature, process_turn_on_params, ) from homeassistant.components.siren.const import ( @@ -20,11 +21,6 @@ from homeassistant.components.siren.const import ( ATTR_DURATION, ATTR_TONE, ATTR_VOLUME_LEVEL, - SUPPORT_DURATION, - SUPPORT_TONES, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_SET, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -103,12 +99,12 @@ MQTT_SIREN_ATTRIBUTES_BLOCKED = frozenset( } ) -SUPPORTED_BASE = SUPPORT_TURN_OFF | SUPPORT_TURN_ON +SUPPORTED_BASE = SirenEntityFeature.TURN_OFF | SirenEntityFeature.TURN_ON SUPPORTED_ATTRIBUTES = { - ATTR_DURATION: SUPPORT_DURATION, - ATTR_TONE: SUPPORT_TONES, - ATTR_VOLUME_LEVEL: SUPPORT_VOLUME_SET, + ATTR_DURATION: SirenEntityFeature.DURATION, + ATTR_TONE: SirenEntityFeature.TONES, + ATTR_VOLUME_LEVEL: SirenEntityFeature.VOLUME_SET, } _LOGGER = logging.getLogger(__name__) @@ -182,16 +178,16 @@ class MqttSiren(MqttEntity, SirenEntity): self._state_off = state_off if state_off else config[CONF_PAYLOAD_OFF] if config[CONF_SUPPORT_DURATION]: - self._supported_features |= SUPPORT_DURATION + self._supported_features |= SirenEntityFeature.DURATION self._attr_extra_state_attributes[ATTR_DURATION] = None if config.get(CONF_AVAILABLE_TONES): - self._supported_features |= SUPPORT_TONES + self._supported_features |= SirenEntityFeature.TONES self._attr_available_tones = config[CONF_AVAILABLE_TONES] self._attr_extra_state_attributes[ATTR_TONE] = None if config[CONF_SUPPORT_VOLUME_SET]: - self._supported_features |= SUPPORT_VOLUME_SET + self._supported_features |= SirenEntityFeature.VOLUME_SET self._attr_extra_state_attributes[ATTR_VOLUME_LEVEL] = None self._optimistic = config[CONF_OPTIMISTIC] or CONF_STATE_TOPIC not in config diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index a2541c064c0..5bfbbd73bce 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -1,40 +1,31 @@ """Provides tag scanning for MQTT.""" +from __future__ import annotations + import functools -import logging import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE, CONF_PLATFORM, CONF_VALUE_TEMPLATE -from homeassistant.helpers import device_registry as dr +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers.typing import ConfigType from . import MqttValueTemplate, subscription from .. import mqtt -from .const import ( - ATTR_DISCOVERY_HASH, - ATTR_DISCOVERY_TOPIC, - CONF_QOS, - CONF_TOPIC, - DOMAIN, -) -from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_UPDATED, clear_discovery_hash +from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC from .mixins import ( - CONF_CONNECTIONS, - CONF_IDENTIFIERS, MQTT_ENTITY_DEVICE_INFO_SCHEMA, - async_removed_from_device, + MqttDiscoveryDeviceUpdate, async_setup_entry_helper, - cleanup_device_registry, - device_info_from_config, + send_discovery_done, + update_device, ) +from .models import ReceiveMessage +from .subscription import EntitySubscription from .util import valid_subscribe_topic -_LOGGER = logging.getLogger(__name__) +LOG_NAME = "Tag" TAG = "tag" TAGS = "mqtt_tags" @@ -50,35 +41,27 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( ) -async def async_setup_entry(hass, config_entry): - """Set up MQTT tag scan dynamically through MQTT discovery.""" - setup = functools.partial(async_setup_tag, hass, config_entry=config_entry) - await async_setup_entry_helper(hass, "tag", setup, PLATFORM_SCHEMA) +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Set up MQTT device automation dynamically through MQTT discovery.""" + + setup = functools.partial(_async_setup_tag, hass, config_entry=config_entry) + await async_setup_entry_helper(hass, TAG, setup, PLATFORM_SCHEMA) -async def async_setup_tag(hass, config, config_entry, discovery_data): +async def _async_setup_tag( + hass: HomeAssistant, + config: ConfigType, + config_entry: ConfigEntry, + discovery_data: dict, +) -> None: """Set up the MQTT tag scanner.""" discovery_hash = discovery_data[ATTR_DISCOVERY_HASH] discovery_id = discovery_hash[1] - device_id = None - if CONF_DEVICE in config: - _update_device(hass, config_entry, config) - - device_registry = dr.async_get(hass) - device = device_registry.async_get_device( - {(DOMAIN, id_) for id_ in config[CONF_DEVICE][CONF_IDENTIFIERS]}, - {tuple(x) for x in config[CONF_DEVICE][CONF_CONNECTIONS]}, - ) - - if device is None: - return - device_id = device.id - - if TAGS not in hass.data: - hass.data[TAGS] = {} - if device_id not in hass.data[TAGS]: - hass.data[TAGS][device_id] = {} + device_id = update_device(hass, config_entry, config) + hass.data.setdefault(TAGS, {}) + if device_id not in hass.data[TAGS]: + hass.data[TAGS][device_id] = {} tag_scanner = MQTTTagScanner( hass, @@ -88,91 +71,65 @@ async def async_setup_tag(hass, config, config_entry, discovery_data): config_entry, ) - await tag_scanner.setup() + await tag_scanner.subscribe_topics() if device_id: hass.data[TAGS][device_id][discovery_id] = tag_scanner + send_discovery_done(hass, discovery_data) -def async_has_tags(hass, device_id): + +def async_has_tags(hass: HomeAssistant, device_id: str) -> bool: """Device has tag scanners.""" if TAGS not in hass.data or device_id not in hass.data[TAGS]: return False return hass.data[TAGS][device_id] != {} -class MQTTTagScanner: +class MQTTTagScanner(MqttDiscoveryDeviceUpdate): """MQTT Tag scanner.""" - def __init__(self, hass, config, device_id, discovery_data, config_entry): + def __init__( + self, + hass: HomeAssistant, + config: ConfigType, + device_id: str | None, + discovery_data: dict, + config_entry: ConfigEntry, + ) -> None: """Initialize.""" self._config = config self._config_entry = config_entry self.device_id = device_id self.discovery_data = discovery_data self.hass = hass - self._remove_discovery = None - self._remove_device_updated = None - self._sub_state = None - self._value_template = None - - self._setup_from_config(config) - - async def discovery_update(self, payload): - """Handle discovery update.""" - discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] - _LOGGER.info( - "Got update for tag scanner with hash: %s '%s'", discovery_hash, payload - ) - if not payload: - # Empty payload: Remove tag scanner - _LOGGER.info("Removing tag scanner: %s", discovery_hash) - self.tear_down() - if self.device_id: - await cleanup_device_registry( - self.hass, self.device_id, self._config_entry.entry_id - ) - else: - # Non-empty payload: Update tag scanner - _LOGGER.info("Updating tag scanner: %s", discovery_hash) - config = PLATFORM_SCHEMA(payload) - self._config = config - if self.device_id: - _update_device(self.hass, self._config_entry, config) - self._setup_from_config(config) - await self.subscribe_topics() - - async_dispatcher_send( - self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None - ) - - def _setup_from_config(self, config): + self._sub_state: dict[str, EntitySubscription] | None = None self._value_template = MqttValueTemplate( config.get(CONF_VALUE_TEMPLATE), hass=self.hass, ).async_render_with_possible_json_value - async def setup(self): - """Set up the MQTT tag scanner.""" - discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] - await self.subscribe_topics() - if self.device_id: - self._remove_device_updated = self.hass.bus.async_listen( - EVENT_DEVICE_REGISTRY_UPDATED, self.device_updated - ) - self._remove_discovery = async_dispatcher_connect( - self.hass, - MQTT_DISCOVERY_UPDATED.format(discovery_hash), - self.discovery_update, - ) - async_dispatcher_send( - self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None + MqttDiscoveryDeviceUpdate.__init__( + self, hass, discovery_data, device_id, config_entry, LOG_NAME ) - async def subscribe_topics(self): + async def async_update(self, discovery_data: dict) -> None: + """Handle MQTT tag discovery updates.""" + # Update tag scanner + config = PLATFORM_SCHEMA(discovery_data) + self._config = config + self._value_template = MqttValueTemplate( + config.get(CONF_VALUE_TEMPLATE), + hass=self.hass, + ).async_render_with_possible_json_value + update_device(self.hass, self._config_entry, config) + await self.subscribe_topics() + + async def subscribe_topics(self) -> None: """Subscribe to MQTT topics.""" - async def tag_scanned(msg): + @callback + async def tag_scanned(msg: ReceiveMessage) -> None: tag_id = self._value_template(msg.payload, "").strip() if not tag_id: # No output from template, ignore return @@ -195,44 +152,12 @@ class MQTTTagScanner: ) await subscription.async_subscribe_topics(self.hass, self._sub_state) - async def device_updated(self, event): - """Handle the update or removal of a device.""" - if not async_removed_from_device( - self.hass, event, self.device_id, self._config_entry.entry_id - ): - return - - # Stop subscribing to discovery updates to not trigger when we clear the - # discovery topic - self.tear_down() - - # Clear the discovery topic so the entity is not rediscovered after a restart - discovery_topic = self.discovery_data[ATTR_DISCOVERY_TOPIC] - mqtt.publish(self.hass, discovery_topic, "", retain=True) - - def tear_down(self): + async def async_tear_down(self) -> None: """Cleanup tag scanner.""" discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] discovery_id = discovery_hash[1] - - clear_discovery_hash(self.hass, discovery_hash) - if self.device_id: - self._remove_device_updated() - self._remove_discovery() - self._sub_state = subscription.async_unsubscribe_topics( self.hass, self._sub_state ) if self.device_id: self.hass.data[TAGS][self.device_id].pop(discovery_id) - - -def _update_device(hass, config_entry, config): - """Update device registry.""" - device_registry = dr.async_get(hass) - config_entry_id = config_entry.entry_id - device_info = device_info_from_config(config[CONF_DEVICE]) - - if config_entry_id is not None and device_info is not None: - device_info["config_entry_id"] = config_entry_id - device_registry.async_get_or_create(**device_info) diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index b8fca50a153..66eec1bdfe8 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -31,6 +31,15 @@ def valid_topic(value: Any) -> str: ) if "\0" in value: raise vol.Invalid("MQTT topic name/filter must not contain null character.") + if any(char <= "\u001F" for char in value): + raise vol.Invalid("MQTT topic name/filter must not contain control characters.") + if any("\u007f" <= char <= "\u009F" for char in value): + raise vol.Invalid("MQTT topic name/filter must not contain control characters.") + if any("\ufdd0" <= char <= "\ufdef" for char in value): + raise vol.Invalid("MQTT topic name/filter must not contain non-characters.") + if any((ord(char) & 0xFFFF) in (0xFFFE, 0xFFFF) for char in value): + raise vol.Invalid("MQTT topic name/filter must not contain noncharacters.") + return value diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 087de1086b5..41d0c9faf17 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -6,18 +6,8 @@ import voluptuous as vol from homeassistant.components.vacuum import ( ATTR_STATUS, ENTITY_ID_FORMAT, - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_STATUS, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, VacuumEntity, + VacuumEntityFeature, ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME from homeassistant.core import callback @@ -33,36 +23,36 @@ from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services SERVICE_TO_STRING = { - SUPPORT_TURN_ON: "turn_on", - SUPPORT_TURN_OFF: "turn_off", - SUPPORT_PAUSE: "pause", - SUPPORT_STOP: "stop", - SUPPORT_RETURN_HOME: "return_home", - SUPPORT_FAN_SPEED: "fan_speed", - SUPPORT_BATTERY: "battery", - SUPPORT_STATUS: "status", - SUPPORT_SEND_COMMAND: "send_command", - SUPPORT_LOCATE: "locate", - SUPPORT_CLEAN_SPOT: "clean_spot", + VacuumEntityFeature.TURN_ON: "turn_on", + VacuumEntityFeature.TURN_OFF: "turn_off", + VacuumEntityFeature.PAUSE: "pause", + VacuumEntityFeature.STOP: "stop", + VacuumEntityFeature.RETURN_HOME: "return_home", + VacuumEntityFeature.FAN_SPEED: "fan_speed", + VacuumEntityFeature.BATTERY: "battery", + VacuumEntityFeature.STATUS: "status", + VacuumEntityFeature.SEND_COMMAND: "send_command", + VacuumEntityFeature.LOCATE: "locate", + VacuumEntityFeature.CLEAN_SPOT: "clean_spot", } STRING_TO_SERVICE = {v: k for k, v in SERVICE_TO_STRING.items()} DEFAULT_SERVICES = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_STOP - | SUPPORT_RETURN_HOME - | SUPPORT_STATUS - | SUPPORT_BATTERY - | SUPPORT_CLEAN_SPOT + VacuumEntityFeature.TURN_ON + | VacuumEntityFeature.TURN_OFF + | VacuumEntityFeature.STOP + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT ) ALL_SERVICES = ( DEFAULT_SERVICES - | SUPPORT_PAUSE - | SUPPORT_LOCATE - | SUPPORT_FAN_SPEED - | SUPPORT_SEND_COMMAND + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.SEND_COMMAND ) CONF_SUPPORTED_FEATURES = ATTR_SUPPORTED_FEATURES @@ -372,7 +362,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): def battery_icon(self): """Return the battery icon for the vacuum cleaner. - No need to check SUPPORT_BATTERY, this won't be called if battery_level is None. + No need to check VacuumEntityFeature.BATTERY, this won't be called if battery_level is None. """ return icon_for_battery_level( battery_level=self.battery_level, charging=self._charging @@ -385,7 +375,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_turn_on(self, **kwargs): """Turn the vacuum on.""" - if self.supported_features & SUPPORT_TURN_ON == 0: + if self.supported_features & VacuumEntityFeature.TURN_ON == 0: return await self.async_publish( @@ -400,7 +390,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_turn_off(self, **kwargs): """Turn the vacuum off.""" - if self.supported_features & SUPPORT_TURN_OFF == 0: + if self.supported_features & VacuumEntityFeature.TURN_OFF == 0: return None await self.async_publish( @@ -415,7 +405,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_stop(self, **kwargs): """Stop the vacuum.""" - if self.supported_features & SUPPORT_STOP == 0: + if self.supported_features & VacuumEntityFeature.STOP == 0: return None await self.async_publish( @@ -430,7 +420,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_clean_spot(self, **kwargs): """Perform a spot clean-up.""" - if self.supported_features & SUPPORT_CLEAN_SPOT == 0: + if self.supported_features & VacuumEntityFeature.CLEAN_SPOT == 0: return None await self.async_publish( @@ -445,7 +435,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_locate(self, **kwargs): """Locate the vacuum (usually by playing a song).""" - if self.supported_features & SUPPORT_LOCATE == 0: + if self.supported_features & VacuumEntityFeature.LOCATE == 0: return None await self.async_publish( @@ -460,7 +450,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_start_pause(self, **kwargs): """Start, pause or resume the cleaning task.""" - if self.supported_features & SUPPORT_PAUSE == 0: + if self.supported_features & VacuumEntityFeature.PAUSE == 0: return None await self.async_publish( @@ -475,7 +465,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_return_to_base(self, **kwargs): """Tell the vacuum to return to its dock.""" - if self.supported_features & SUPPORT_RETURN_HOME == 0: + if self.supported_features & VacuumEntityFeature.RETURN_HOME == 0: return None await self.async_publish( @@ -491,7 +481,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_set_fan_speed(self, fan_speed, **kwargs): """Set fan speed.""" if ( - self.supported_features & SUPPORT_FAN_SPEED == 0 + self.supported_features & VacuumEntityFeature.FAN_SPEED == 0 ) or fan_speed not in self._fan_speed_list: return None @@ -507,7 +497,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): async def async_send_command(self, command, params=None, **kwargs): """Send a command to a vacuum cleaner.""" - if self.supported_features & SUPPORT_SEND_COMMAND == 0: + if self.supported_features & VacuumEntityFeature.SEND_COMMAND == 0: return if params: message = {"command": command} diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index e5c138c96ff..7a5424d7cbf 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -9,17 +9,8 @@ from homeassistant.components.vacuum import ( STATE_DOCKED, STATE_ERROR, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_START, - SUPPORT_STATUS, - SUPPORT_STOP, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, @@ -45,35 +36,35 @@ from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services SERVICE_TO_STRING = { - SUPPORT_START: "start", - SUPPORT_PAUSE: "pause", - SUPPORT_STOP: "stop", - SUPPORT_RETURN_HOME: "return_home", - SUPPORT_FAN_SPEED: "fan_speed", - SUPPORT_BATTERY: "battery", - SUPPORT_STATUS: "status", - SUPPORT_SEND_COMMAND: "send_command", - SUPPORT_LOCATE: "locate", - SUPPORT_CLEAN_SPOT: "clean_spot", + VacuumEntityFeature.START: "start", + VacuumEntityFeature.PAUSE: "pause", + VacuumEntityFeature.STOP: "stop", + VacuumEntityFeature.RETURN_HOME: "return_home", + VacuumEntityFeature.FAN_SPEED: "fan_speed", + VacuumEntityFeature.BATTERY: "battery", + VacuumEntityFeature.STATUS: "status", + VacuumEntityFeature.SEND_COMMAND: "send_command", + VacuumEntityFeature.LOCATE: "locate", + VacuumEntityFeature.CLEAN_SPOT: "clean_spot", } STRING_TO_SERVICE = {v: k for k, v in SERVICE_TO_STRING.items()} DEFAULT_SERVICES = ( - SUPPORT_START - | SUPPORT_STOP - | SUPPORT_RETURN_HOME - | SUPPORT_STATUS - | SUPPORT_BATTERY - | SUPPORT_CLEAN_SPOT + VacuumEntityFeature.START + | VacuumEntityFeature.STOP + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT ) ALL_SERVICES = ( DEFAULT_SERVICES - | SUPPORT_PAUSE - | SUPPORT_LOCATE - | SUPPORT_FAN_SPEED - | SUPPORT_SEND_COMMAND + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.SEND_COMMAND ) BATTERY = "battery_level" @@ -258,7 +249,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_start(self): """Start the vacuum.""" - if self.supported_features & SUPPORT_START == 0: + if self.supported_features & VacuumEntityFeature.START == 0: return None await self.async_publish( self._command_topic, @@ -270,7 +261,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_pause(self): """Pause the vacuum.""" - if self.supported_features & SUPPORT_PAUSE == 0: + if self.supported_features & VacuumEntityFeature.PAUSE == 0: return None await self.async_publish( self._command_topic, @@ -282,7 +273,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_stop(self, **kwargs): """Stop the vacuum.""" - if self.supported_features & SUPPORT_STOP == 0: + if self.supported_features & VacuumEntityFeature.STOP == 0: return None await self.async_publish( self._command_topic, @@ -294,7 +285,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_set_fan_speed(self, fan_speed, **kwargs): """Set fan speed.""" - if (self.supported_features & SUPPORT_FAN_SPEED == 0) or ( + if (self.supported_features & VacuumEntityFeature.FAN_SPEED == 0) or ( fan_speed not in self._fan_speed_list ): return None @@ -308,7 +299,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_return_to_base(self, **kwargs): """Tell the vacuum to return to its dock.""" - if self.supported_features & SUPPORT_RETURN_HOME == 0: + if self.supported_features & VacuumEntityFeature.RETURN_HOME == 0: return None await self.async_publish( self._command_topic, @@ -320,7 +311,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_clean_spot(self, **kwargs): """Perform a spot clean-up.""" - if self.supported_features & SUPPORT_CLEAN_SPOT == 0: + if self.supported_features & VacuumEntityFeature.CLEAN_SPOT == 0: return None await self.async_publish( self._command_topic, @@ -332,7 +323,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_locate(self, **kwargs): """Locate the vacuum (usually by playing a song).""" - if self.supported_features & SUPPORT_LOCATE == 0: + if self.supported_features & VacuumEntityFeature.LOCATE == 0: return None await self.async_publish( self._command_topic, @@ -344,7 +335,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): async def async_send_command(self, command, params=None, **kwargs): """Send a command to a vacuum cleaner.""" - if self.supported_features & SUPPORT_SEND_COMMAND == 0: + if self.supported_features & VacuumEntityFeature.SEND_COMMAND == 0: return None if params: message = {"command": command} diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py index 3ffccb1bd56..62a10c1bd00 100644 --- a/homeassistant/components/mqtt_eventstream/__init__.py +++ b/homeassistant/components/mqtt_eventstream/__init__.py @@ -14,7 +14,6 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, MATCH_ALL, ) from homeassistant.core import EventOrigin, HomeAssistant, State, callback @@ -59,7 +58,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: pub_topic = conf.get(CONF_PUBLISH_TOPIC) sub_topic = conf.get(CONF_SUBSCRIBE_TOPIC) ignore_event = conf.get(CONF_IGNORE_EVENT) - ignore_event.append(EVENT_TIME_CHANGED) async def _event_publisher(event): """Handle events by publishing them on the MQTT queue.""" diff --git a/homeassistant/components/mullvad/binary_sensor.py b/homeassistant/components/mullvad/binary_sensor.py index 871de342c23..f39f05dd430 100644 --- a/homeassistant/components/mullvad/binary_sensor.py +++ b/homeassistant/components/mullvad/binary_sensor.py @@ -29,31 +29,23 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN] async_add_entities( - MullvadBinarySensor(coordinator, sensor) for sensor in BINARY_SENSORS + MullvadBinarySensor(coordinator, sensor, config_entry) + for sensor in BINARY_SENSORS ) class MullvadBinarySensor(CoordinatorEntity, BinarySensorEntity): """Represents a Mullvad binary sensor.""" - def __init__(self, coordinator, sensor): + def __init__(self, coordinator, sensor, config_entry): """Initialize the Mullvad binary sensor.""" super().__init__(coordinator) - self.id = sensor[CONF_ID] - self._name = sensor[CONF_NAME] - self._device_class = sensor[CONF_DEVICE_CLASS] - - @property - def device_class(self): - """Return the device class for this binary sensor.""" - return self._device_class - - @property - def name(self): - """Return the name for this binary sensor.""" - return self._name + self._sensor = sensor + self._attr_device_class = sensor[CONF_DEVICE_CLASS] + self._attr_name = sensor[CONF_NAME] + self._attr_unique_id = f"{config_entry.entry_id}_{sensor[CONF_ID]}" @property def is_on(self): """Return the state for this binary sensor.""" - return self.coordinator.data[self.id] + return self.coordinator.data[self._sensor[CONF_ID]] diff --git a/homeassistant/components/myq/cover.py b/homeassistant/components/myq/cover.py index f3bc27a8453..861ae379007 100644 --- a/homeassistant/components/myq/cover.py +++ b/homeassistant/components/myq/cover.py @@ -3,10 +3,9 @@ from pymyq.const import DEVICE_TYPE_GATE as MYQ_DEVICE_TYPE_GATE from pymyq.errors import MyQError from homeassistant.components.cover import ( - SUPPORT_CLOSE, - SUPPORT_OPEN, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING @@ -36,7 +35,7 @@ async def async_setup_entry( class MyQCover(MyQEntity, CoverEntity): """Representation of a MyQ cover.""" - _attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE def __init__(self, coordinator, device): """Initialize with API object, device id.""" diff --git a/homeassistant/components/myq/light.py b/homeassistant/components/myq/light.py index d3f6c6de36b..1c001eac7fe 100644 --- a/homeassistant/components/myq/light.py +++ b/homeassistant/components/myq/light.py @@ -1,7 +1,7 @@ """Support for MyQ-Enabled lights.""" from pymyq.errors import MyQError -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant @@ -30,7 +30,8 @@ async def async_setup_entry( class MyQLight(MyQEntity, LightEntity): """Representation of a MyQ light.""" - _attr_supported_features = 0 + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} @property def is_on(self): diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index 0157d06ca2e..6a6640f7bdd 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -8,13 +8,8 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -31,20 +26,20 @@ from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .helpers import on_unload DICT_HA_TO_MYS = { - HVAC_MODE_AUTO: "AutoChangeOver", - HVAC_MODE_COOL: "CoolOn", - HVAC_MODE_HEAT: "HeatOn", - HVAC_MODE_OFF: "Off", + HVACMode.AUTO: "AutoChangeOver", + HVACMode.COOL: "CoolOn", + HVACMode.HEAT: "HeatOn", + HVACMode.OFF: "Off", } DICT_MYS_TO_HA = { - "AutoChangeOver": HVAC_MODE_AUTO, - "CoolOn": HVAC_MODE_COOL, - "HeatOn": HVAC_MODE_HEAT, - "Off": HVAC_MODE_OFF, + "AutoChangeOver": HVACMode.AUTO, + "CoolOn": HVACMode.COOL, + "HeatOn": HVACMode.HEAT, + "Off": HVACMode.OFF, } FAN_LIST = ["Auto", "Min", "Normal", "Max"] -OPERATION_LIST = [HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT] +OPERATION_LIST = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT] async def async_setup_entry( @@ -78,20 +73,22 @@ async def async_setup_entry( class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity): """Representation of a MySensors HVAC.""" + _attr_hvac_modes = OPERATION_LIST + @property def supported_features(self) -> int: """Return the list of supported features.""" features = 0 set_req = self.gateway.const.SetReq if set_req.V_HVAC_SPEED in self._values: - features = features | SUPPORT_FAN_MODE + features = features | ClimateEntityFeature.FAN_MODE if ( set_req.V_HVAC_SETPOINT_COOL in self._values and set_req.V_HVAC_SETPOINT_HEAT in self._values ): - features = features | SUPPORT_TARGET_TEMPERATURE_RANGE + features = features | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE else: - features = features | SUPPORT_TARGET_TEMPERATURE + features = features | ClimateEntityFeature.TARGET_TEMPERATURE return features @property @@ -145,14 +142,9 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity): return None @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" - return self._values.get(self.value_type, HVAC_MODE_HEAT) - - @property - def hvac_modes(self) -> list[str]: - """List of available operation modes.""" - return OPERATION_LIST + return self._values.get(self.value_type, HVACMode.HEAT) @property def fan_mode(self) -> str | None: @@ -207,7 +199,7 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity): self._values[set_req.V_HVAC_SPEED] = fan_mode self.async_write_ha_state() - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target temperature.""" self.gateway.set_child_value( self.node_id, diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index e45a332767f..9d992e172b0 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -309,7 +309,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): try: await self.hass.async_add_executor_job( - verification_func, user_input.get(CONF_DEVICE) + verification_func, user_input[CONF_DEVICE] ) except vol.Invalid: errors[CONF_DEVICE] = ( diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index 870cde863c4..e2c676220fa 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -8,9 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_RGBW_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -136,8 +134,8 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity): class MySensorsLightDimmer(MySensorsLight): """Dimmer child class to MySensorsLight.""" - _attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} - _attr_color_mode = COLOR_MODE_BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_color_mode = ColorMode.BRIGHTNESS async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" @@ -156,8 +154,8 @@ class MySensorsLightDimmer(MySensorsLight): class MySensorsLightRGB(MySensorsLight): """RGB child class to MySensorsLight.""" - _attr_supported_color_modes = {COLOR_MODE_RGB} - _attr_color_mode = COLOR_MODE_RGB + _attr_supported_color_modes = {ColorMode.RGB} + _attr_color_mode = ColorMode.RGB async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" @@ -202,8 +200,8 @@ class MySensorsLightRGB(MySensorsLight): class MySensorsLightRGBW(MySensorsLightRGB): """RGBW child class to MySensorsLightRGB.""" - _attr_supported_color_modes = {COLOR_MODE_RGBW} - _attr_color_mode = COLOR_MODE_RGBW + _attr_supported_color_modes = {ColorMode.RGBW} + _attr_color_mode = ColorMode.RGBW async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py index 62b82a0214b..f6f214301df 100644 --- a/homeassistant/components/mystrom/light.py +++ b/homeassistant/components/mystrom/light.py @@ -12,11 +12,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_EFFECT, - SUPPORT_FLASH, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.core import HomeAssistant @@ -29,8 +27,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "myStrom bulb" -SUPPORT_MYSTROM = SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_FLASH | SUPPORT_COLOR - EFFECT_RAINBOW = "rainbow" EFFECT_SUNRISE = "sunrise" @@ -74,6 +70,10 @@ async def async_setup_platform( class MyStromLight(LightEntity): """Representation of the myStrom WiFi bulb.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + _attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.FLASH + def __init__(self, bulb, name, mac): """Initialize the light.""" self._bulb = bulb @@ -95,11 +95,6 @@ class MyStromLight(LightEntity): """Return a unique ID.""" return self._mac - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_MYSTROM - @property def brightness(self): """Return the brightness of the light.""" diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index d9516496e69..2bcd7958de9 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -4,14 +4,10 @@ from __future__ import annotations from nad_receiver import NADReceiver, NADReceiverTCP, NADReceiverTelnet import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -35,12 +31,12 @@ DEFAULT_MAX_VOLUME = -20 DEFAULT_VOLUME_STEP = 4 SUPPORT_NAD = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_STEP - | SUPPORT_SELECT_SOURCE + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.SELECT_SOURCE ) CONF_SERIAL_PORT = "serial_port" # for NADReceiver @@ -90,6 +86,8 @@ def setup_platform( class NAD(MediaPlayerEntity): """Representation of a NAD Receiver.""" + _attr_supported_features = SUPPORT_NAD + def __init__(self, config): """Initialize the NAD Receiver device.""" self.config = config @@ -134,11 +132,6 @@ class NAD(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._mute - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_NAD - def turn_off(self): """Turn the media player off.""" self._nad_receiver.main_power("=", "Off") @@ -227,6 +220,8 @@ class NAD(MediaPlayerEntity): class NADtcp(MediaPlayerEntity): """Representation of a NAD Digital amplifier.""" + _attr_supported_features = SUPPORT_NAD + def __init__(self, config): """Initialize the amplifier.""" self._name = config[CONF_NAME] @@ -261,11 +256,6 @@ class NADtcp(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._mute - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_NAD - def turn_off(self): """Turn the media player off.""" self._nad_receiver.power_off() diff --git a/homeassistant/components/nam/__init__.py b/homeassistant/components/nam/__init__.py index 53788477c48..6b0f9db3757 100644 --- a/homeassistant/components/nam/__init__.py +++ b/homeassistant/components/nam/__init__.py @@ -108,10 +108,7 @@ class NAMDataUpdateCoordinator(DataUpdateCoordinator): async def _async_update_data(self) -> NAMSensors: """Update data via library.""" try: - # Device firmware uses synchronous code and doesn't respond to http queries - # when reading data from sensors. The nettigo-air-quality library tries to - # get the data 4 times, so we use a longer than usual timeout here. - async with async_timeout.timeout(30): + async with async_timeout.timeout(10): data = await self.nam.async_update() # We do not need to catch AuthFailed exception here because sensor data is # always available without authorization. diff --git a/homeassistant/components/nam/config_flow.py b/homeassistant/components/nam/config_flow.py index 82032ac306e..1727ddff162 100644 --- a/homeassistant/components/nam/config_flow.py +++ b/homeassistant/components/nam/config_flow.py @@ -39,10 +39,8 @@ async def async_get_mac(hass: HomeAssistant, host: str, data: dict[str, Any]) -> options = ConnectionOptions(host, data.get(CONF_USERNAME), data.get(CONF_PASSWORD)) nam = await NettigoAirMonitor.create(websession, options) - # Device firmware uses synchronous code and doesn't respond to http queries - # when reading data from sensors. The nettigo-air-monitor library tries to get - # the data 4 times, so we use a longer than usual timeout here. - async with async_timeout.timeout(30): + + async with async_timeout.timeout(10): return await nam.async_get_mac_address() diff --git a/homeassistant/components/nam/manifest.json b/homeassistant/components/nam/manifest.json index 64c3d2fb0f7..2b62500f23b 100644 --- a/homeassistant/components/nam/manifest.json +++ b/homeassistant/components/nam/manifest.json @@ -3,7 +3,7 @@ "name": "Nettigo Air Monitor", "documentation": "https://www.home-assistant.io/integrations/nam", "codeowners": ["@bieniu"], - "requirements": ["nettigo-air-monitor==1.2.1"], + "requirements": ["nettigo-air-monitor==1.2.2"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/homeassistant/components/nam/translations/hu.json b/homeassistant/components/nam/translations/hu.json index f0cf505163c..e0aa942afd8 100644 --- a/homeassistant/components/nam/translations/hu.json +++ b/homeassistant/components/nam/translations/hu.json @@ -11,7 +11,7 @@ "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "unknown": "V\u00e1ratlan hiba" }, - "flow_title": "{name}", + "flow_title": "{host}", "step": { "confirm_discovery": { "description": "Szeretn\u00e9 be\u00e1ll\u00edtani Nettigo Air Monitor-ot a {host} c\u00edmen?" diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 1cf6bd4d8bf..1a43cb4989a 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -12,12 +12,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -47,6 +44,9 @@ async def async_setup_entry( class NanoleafLight(NanoleafEntity, LightEntity): """Representation of a Nanoleaf Light.""" + _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} + _attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION + def __init__(self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator) -> None: """Initialize the Nanoleaf light.""" super().__init__(nanoleaf, coordinator) @@ -97,15 +97,14 @@ class NanoleafLight(NanoleafEntity, LightEntity): return self._nanoleaf.hue, self._nanoleaf.saturation @property - def supported_features(self) -> int: - """Flag supported features.""" - return ( - SUPPORT_BRIGHTNESS - | SUPPORT_COLOR_TEMP - | SUPPORT_EFFECT - | SUPPORT_COLOR - | SUPPORT_TRANSITION - ) + def color_mode(self) -> ColorMode | None: + """Return the color mode of the light.""" + # According to API docs, color mode is "ct", "effect" or "hs" + # https://forum.nanoleaf.me/docs/openapi#_4qgqrz96f44d + if self._nanoleaf.color_mode == "ct": + return ColorMode.COLOR_TEMP + # Home Assistant does not have an "effect" color mode, just report hs + return ColorMode.HS async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the light to turn on.""" diff --git a/homeassistant/components/nanoleaf/translations/fr.json b/homeassistant/components/nanoleaf/translations/fr.json index 5893635580b..5a9a8022ecb 100644 --- a/homeassistant/components/nanoleaf/translations/fr.json +++ b/homeassistant/components/nanoleaf/translations/fr.json @@ -24,5 +24,13 @@ } } } + }, + "device_automation": { + "trigger_type": { + "swipe_down": "Balayer vers le bas", + "swipe_left": "Balayer vers la gauche", + "swipe_right": "Balayer vers la droite", + "swipe_up": "Balayer vers le haut" + } } } \ No newline at end of file diff --git a/homeassistant/components/nanoleaf/translations/hu.json b/homeassistant/components/nanoleaf/translations/hu.json index c67c4f958de..03ed5c92828 100644 --- a/homeassistant/components/nanoleaf/translations/hu.json +++ b/homeassistant/components/nanoleaf/translations/hu.json @@ -15,7 +15,7 @@ "flow_title": "{name}", "step": { "link": { - "description": "Nyomja meg \u00e9s tartsa lenyomva a Nanoleaf bekapcsol\u00f3gombj\u00e1t 5 m\u00e1sodpercig, am\u00edg a gomb LED-je villogni nem kezd, majd kattintson a **K\u00fcld\u00e9s** gombra 30 m\u00e1sodpercen bel\u00fcl.", + "description": "Nyomja meg \u00e9s tartsa lenyomva a Nanoleaf bekapcsol\u00f3gombj\u00e1t 5 m\u00e1sodpercig, am\u00edg a gomb LED-je villogni nem kezd, majd kattintson a **Mehet** gombra 30 m\u00e1sodpercen bel\u00fcl.", "title": "Nanoleaf link" }, "user": { diff --git a/homeassistant/components/neato/manifest.json b/homeassistant/components/neato/manifest.json index b183548222d..f12f79c77a8 100644 --- a/homeassistant/components/neato/manifest.json +++ b/homeassistant/components/neato/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/neato", "requirements": ["pybotvac==0.0.23"], "codeowners": ["@dshokouhi", "@Santobert"], - "dependencies": ["http"], + "dependencies": ["auth"], "iot_class": "cloud_polling", "loggers": ["pybotvac"] } diff --git a/homeassistant/components/neato/translations/hu.json b/homeassistant/components/neato/translations/hu.json index 281c5cd61a9..255378eda35 100644 --- a/homeassistant/components/neato/translations/hu.json +++ b/homeassistant/components/neato/translations/hu.json @@ -4,7 +4,7 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, "create_entry": { @@ -12,7 +12,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "reauth_confirm": { "title": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" diff --git a/homeassistant/components/neato/translations/it.json b/homeassistant/components/neato/translations/it.json index 51d9119eb2c..000625a0294 100644 --- a/homeassistant/components/neato/translations/it.json +++ b/homeassistant/components/neato/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index c40509c7556..3a4096055bc 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -15,16 +15,8 @@ from homeassistant.components.vacuum import ( STATE_DOCKED, STATE_ERROR, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_LOCATE, - SUPPORT_MAP, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STOP, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_MODE, STATE_IDLE, STATE_PAUSED @@ -51,18 +43,6 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(minutes=SCAN_INTERVAL_MINUTES) -SUPPORT_NEATO = ( - SUPPORT_BATTERY - | SUPPORT_PAUSE - | SUPPORT_RETURN_HOME - | SUPPORT_STOP - | SUPPORT_START - | SUPPORT_CLEAN_SPOT - | SUPPORT_STATE - | SUPPORT_MAP - | SUPPORT_LOCATE -) - ATTR_CLEAN_START = "clean_start" ATTR_CLEAN_STOP = "clean_stop" ATTR_CLEAN_AREA = "clean_area" @@ -114,6 +94,18 @@ async def async_setup_entry( class NeatoConnectedVacuum(StateVacuumEntity): """Representation of a Neato Connected Vacuum.""" + _attr_supported_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.STOP + | VacuumEntityFeature.START + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.STATE + | VacuumEntityFeature.MAP + | VacuumEntityFeature.LOCATE + ) + def __init__( self, neato: NeatoHub, @@ -276,7 +268,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): @property def supported_features(self) -> int: """Flag vacuum cleaner robot features that are supported.""" - return SUPPORT_NEATO + return self._attr_supported_features @property def battery_level(self) -> int | None: diff --git a/homeassistant/components/ness_alarm/alarm_control_panel.py b/homeassistant/components/ness_alarm/alarm_control_panel.py index 8970e346f4c..9fccee6f64f 100644 --- a/homeassistant/components/ness_alarm/alarm_control_panel.py +++ b/homeassistant/components/ness_alarm/alarm_control_panel.py @@ -6,11 +6,7 @@ import logging from nessclient import ArmingState import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_TRIGGER, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING, @@ -45,6 +41,12 @@ async def async_setup_platform( class NessAlarmPanel(alarm.AlarmControlPanelEntity): """Representation of a Ness alarm panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.TRIGGER + ) + def __init__(self, client, name): """Initialize the alarm panel.""" self._client = client @@ -72,18 +74,13 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity): @property def code_format(self): """Return the regex for code format or None if no code is required.""" - return alarm.FORMAT_NUMBER + return alarm.CodeFormat.NUMBER @property def state(self): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_TRIGGER - async def async_alarm_disarm(self, code=None): """Send disarm command.""" await self._client.disarm(code) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 2a5f3850fe4..076b9a58814 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -278,10 +278,10 @@ class NestEventViewBase(HomeAssistantView, ABC): ) try: media = await self.load_media(nest_device, event_token) - except DecodeException as err: - raise HomeAssistantError( - "Even token was invalid: %s" % event_token - ) from err + except DecodeException: + return self._json_error( + f"Event token was invalid '{event_token}'", HTTPStatus.NOT_FOUND + ) except ApiException as err: raise HomeAssistantError("Unable to fetch media for event") from err if not media: diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index 0def2e493c2..a46af2979f4 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -17,8 +17,8 @@ from google_nest_sdm.camera_traits import ( from google_nest_sdm.device import Device from google_nest_sdm.exceptions import ApiException -from homeassistant.components.camera import SUPPORT_STREAM, Camera -from homeassistant.components.camera.const import STREAM_TYPE_WEB_RTC +from homeassistant.components.camera import Camera, CameraEntityFeature +from homeassistant.components.camera.const import StreamType from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, PlatformNotReady @@ -110,17 +110,17 @@ class NestCamera(Camera): """Flag supported features.""" supported_features = 0 if CameraLiveStreamTrait.NAME in self._device.traits: - supported_features |= SUPPORT_STREAM + supported_features |= CameraEntityFeature.STREAM return supported_features @property - def frontend_stream_type(self) -> str | None: + def frontend_stream_type(self) -> StreamType | None: """Return the type of stream supported by this camera.""" if CameraLiveStreamTrait.NAME not in self._device.traits: return None trait = self._device.traits[CameraLiveStreamTrait.NAME] if StreamingProtocol.WEB_RTC in trait.supported_protocols: - return STREAM_TYPE_WEB_RTC + return StreamType.WEB_RTC return super().frontend_stream_type @property @@ -134,7 +134,7 @@ class NestCamera(Camera): async def stream_source(self) -> str | None: """Return the source of the stream.""" - if not self.supported_features & SUPPORT_STREAM: + if not self.supported_features & CameraEntityFeature.STREAM: return None if CameraLiveStreamTrait.NAME not in self._device.traits: return None diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index ff8cffcf7fa..89048b9f624 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -19,24 +19,13 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, FAN_OFF, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_ECO, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -49,11 +38,11 @@ from .const import DATA_SUBSCRIBER, DOMAIN from .device_info import NestDeviceInfo # Mapping for sdm.devices.traits.ThermostatMode mode field -THERMOSTAT_MODE_MAP: dict[str, str] = { - "OFF": HVAC_MODE_OFF, - "HEAT": HVAC_MODE_HEAT, - "COOL": HVAC_MODE_COOL, - "HEATCOOL": HVAC_MODE_HEAT_COOL, +THERMOSTAT_MODE_MAP: dict[str, HVACMode] = { + "OFF": HVACMode.OFF, + "HEAT": HVACMode.HEAT, + "COOL": HVACMode.COOL, + "HEATCOOL": HVACMode.HEAT_COOL, } THERMOSTAT_INV_MODE_MAP = {v: k for k, v in THERMOSTAT_MODE_MAP.items()} @@ -62,12 +51,12 @@ THERMOSTAT_ECO_MODE = "MANUAL_ECO" # Mapping for sdm.devices.traits.ThermostatHvac status field THERMOSTAT_HVAC_STATUS_MAP = { - "OFF": CURRENT_HVAC_OFF, - "HEATING": CURRENT_HVAC_HEAT, - "COOLING": CURRENT_HVAC_COOL, + "OFF": HVACAction.OFF, + "HEATING": HVACAction.HEATING, + "COOLING": HVACAction.COOLING, } -THERMOSTAT_RANGE_MODES = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] +THERMOSTAT_RANGE_MODES = [HVACMode.HEAT_COOL, HVACMode.AUTO] PRESET_MODE_MAP = { "MANUAL_ECO": PRESET_ECO, @@ -82,6 +71,8 @@ FAN_MODE_MAP = { FAN_INV_MODE_MAP = {v: k for k, v in FAN_MODE_MAP.items()} MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API +MIN_TEMP = 10 +MAX_TEMP = 32 async def async_setup_sdm_entry( @@ -105,6 +96,9 @@ async def async_setup_sdm_entry( class ThermostatEntity(ClimateEntity): """A nest thermostat climate entity.""" + _attr_min_temp = MIN_TEMP + _attr_max_temp = MAX_TEMP + def __init__(self, device: Device) -> None: """Initialize ThermostatEntity.""" self._device = device @@ -157,16 +151,16 @@ class ThermostatEntity(ClimateEntity): """Return the temperature currently set to be reached.""" if not (trait := self._target_temperature_trait): return None - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return trait.heat_celsius - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return trait.cool_celsius return None @property def target_temperature_high(self) -> float | None: """Return the upper bound target temperature.""" - if self.hvac_mode != HVAC_MODE_HEAT_COOL: + if self.hvac_mode != HVACMode.HEAT_COOL: return None if not (trait := self._target_temperature_trait): return None @@ -175,7 +169,7 @@ class ThermostatEntity(ClimateEntity): @property def target_temperature_low(self) -> float | None: """Return the lower bound target temperature.""" - if self.hvac_mode != HVAC_MODE_HEAT_COOL: + if self.hvac_mode != HVACMode.HEAT_COOL: return None if not (trait := self._target_temperature_trait): return None @@ -201,26 +195,26 @@ class ThermostatEntity(ClimateEntity): return None @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return the current operation (e.g. heat, cool, idle).""" - hvac_mode = HVAC_MODE_OFF + hvac_mode = HVACMode.OFF if ThermostatModeTrait.NAME in self._device.traits: trait = self._device.traits[ThermostatModeTrait.NAME] if trait.mode in THERMOSTAT_MODE_MAP: hvac_mode = THERMOSTAT_MODE_MAP[trait.mode] - if hvac_mode == HVAC_MODE_OFF and self.fan_mode == FAN_ON: - hvac_mode = HVAC_MODE_FAN_ONLY + if hvac_mode == HVACMode.OFF and self.fan_mode == FAN_ON: + hvac_mode = HVACMode.FAN_ONLY return hvac_mode @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """List of available operation modes.""" supported_modes = [] for mode in self._get_device_hvac_modes: if mode in THERMOSTAT_MODE_MAP: supported_modes.append(THERMOSTAT_MODE_MAP[mode]) - if self.supported_features & SUPPORT_FAN_MODE: - supported_modes.append(HVAC_MODE_FAN_ONLY) + if self.supported_features & ClimateEntityFeature.FAN_MODE: + supported_modes.append(HVACMode.FAN_ONLY) return supported_modes @property @@ -233,14 +227,12 @@ class ThermostatEntity(ClimateEntity): return set(modes) @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current HVAC action (heating, cooling).""" trait = self._device.traits[ThermostatHvacTrait.NAME] - if trait.status == "OFF" and self.hvac_mode != HVAC_MODE_OFF: - return CURRENT_HVAC_IDLE - if trait.status in THERMOSTAT_HVAC_STATUS_MAP: - return THERMOSTAT_HVAC_STATUS_MAP[trait.status] - return None + if trait.status == "OFF" and self.hvac_mode != HVACMode.OFF: + return HVACAction.IDLE + return THERMOSTAT_HVAC_STATUS_MAP.get(trait.status) @property def preset_mode(self) -> str: @@ -285,27 +277,27 @@ class ThermostatEntity(ClimateEntity): def _get_supported_features(self) -> int: """Compute the bitmap of supported features from the current state.""" features = 0 - if HVAC_MODE_HEAT_COOL in self.hvac_modes: - features |= SUPPORT_TARGET_TEMPERATURE_RANGE - if HVAC_MODE_HEAT in self.hvac_modes or HVAC_MODE_COOL in self.hvac_modes: - features |= SUPPORT_TARGET_TEMPERATURE + if HVACMode.HEAT_COOL in self.hvac_modes: + features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + if HVACMode.HEAT in self.hvac_modes or HVACMode.COOL in self.hvac_modes: + features |= ClimateEntityFeature.TARGET_TEMPERATURE if ThermostatEcoTrait.NAME in self._device.traits: - features |= SUPPORT_PRESET_MODE + features |= ClimateEntityFeature.PRESET_MODE if FanTrait.NAME in self._device.traits: # Fan trait may be present without actually support fan mode fan_trait = self._device.traits[FanTrait.NAME] if fan_trait.timer_mode is not None: - features |= SUPPORT_FAN_MODE + features |= ClimateEntityFeature.FAN_MODE return features - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" if hvac_mode not in self.hvac_modes: raise ValueError(f"Unsupported hvac_mode '{hvac_mode}'") - if hvac_mode == HVAC_MODE_FAN_ONLY: + if hvac_mode == HVACMode.FAN_ONLY: # Turn the fan on but also turn off the hvac if it is on await self.async_set_fan_mode(FAN_ON) - hvac_mode = HVAC_MODE_OFF + hvac_mode = HVACMode.OFF api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode] trait = self._device.traits[ThermostatModeTrait.NAME] await trait.set_mode(api_mode) @@ -322,12 +314,12 @@ class ThermostatEntity(ClimateEntity): if ThermostatTemperatureSetpointTrait.NAME not in self._device.traits: return trait = self._device.traits[ThermostatTemperatureSetpointTrait.NAME] - if self.preset_mode == PRESET_ECO or hvac_mode == HVAC_MODE_HEAT_COOL: + if self.preset_mode == PRESET_ECO or hvac_mode == HVACMode.HEAT_COOL: if low_temp and high_temp: await trait.set_range(low_temp, high_temp) - elif hvac_mode == HVAC_MODE_COOL and temp: + elif hvac_mode == HVACMode.COOL and temp: await trait.set_cool(temp) - elif hvac_mode == HVAC_MODE_HEAT and temp: + elif hvac_mode == HVACMode.HEAT and temp: await trait.set_heat(temp) async def async_set_preset_mode(self, preset_mode: str) -> None: diff --git a/homeassistant/components/nest/diagnostics.py b/homeassistant/components/nest/diagnostics.py index 2840c34378b..d178d52393e 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -2,11 +2,14 @@ from __future__ import annotations +from typing import Any + from google_nest_sdm import diagnostics from google_nest_sdm.device import Device from google_nest_sdm.device_traits import InfoTrait from google_nest_sdm.exceptions import ApiException +from homeassistant.components.camera import diagnostics as camera_diagnostics from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry @@ -42,12 +45,18 @@ async def async_get_config_entry_diagnostics( return {"error": str(err)} if not nest_devices: return {} - return { + data: dict[str, Any] = { **diagnostics.get_diagnostics(), "devices": [ nest_device.get_diagnostics() for nest_device in nest_devices.values() ], } + camera_data = await camera_diagnostics.async_get_config_entry_diagnostics( + hass, config_entry + ) + if camera_data: + data["camera"] = camera_data + return data async def async_get_device_diagnostics( diff --git a/homeassistant/components/nest/legacy/camera.py b/homeassistant/components/nest/legacy/camera.py index 3bcf1cdee2c..9974ae2daaa 100644 --- a/homeassistant/components/nest/legacy/camera.py +++ b/homeassistant/components/nest/legacy/camera.py @@ -8,7 +8,7 @@ import logging import requests -from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_ON_OFF, Camera +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, CameraEntityFeature from homeassistant.helpers.entity import DeviceInfo from homeassistant.util.dt import utcnow @@ -38,6 +38,8 @@ async def async_setup_legacy_entry(hass, entry, async_add_entities) -> None: class NestCamera(Camera): """Representation of a Nest Camera.""" + _attr_supported_features = CameraEntityFeature.ON_OFF + def __init__(self, structure, device): """Initialize a Nest Camera.""" super().__init__() @@ -88,11 +90,6 @@ class NestCamera(Camera): """Return the brand of the camera.""" return NEST_BRAND - @property - def supported_features(self): - """Nest Cam support turn on and off.""" - return SUPPORT_ON_OFF - @property def is_on(self): """Return true if on.""" diff --git a/homeassistant/components/nest/legacy/climate.py b/homeassistant/components/nest/legacy/climate.py index 97ed1ee00b9..07238a46ea9 100644 --- a/homeassistant/components/nest/legacy/climate.py +++ b/homeassistant/components/nest/legacy/climate.py @@ -10,22 +10,14 @@ from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, FAN_AUTO, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_ECO, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -51,18 +43,18 @@ NEST_MODE_COOL = "cool" NEST_MODE_OFF = "off" MODE_HASS_TO_NEST = { - HVAC_MODE_AUTO: NEST_MODE_HEAT_COOL, - HVAC_MODE_HEAT: NEST_MODE_HEAT, - HVAC_MODE_COOL: NEST_MODE_COOL, - HVAC_MODE_OFF: NEST_MODE_OFF, + HVACMode.AUTO: NEST_MODE_HEAT_COOL, + HVACMode.HEAT: NEST_MODE_HEAT, + HVACMode.COOL: NEST_MODE_COOL, + HVACMode.OFF: NEST_MODE_OFF, } MODE_NEST_TO_HASS = {v: k for k, v in MODE_HASS_TO_NEST.items()} ACTION_NEST_TO_HASS = { - "off": CURRENT_HVAC_IDLE, - "heating": CURRENT_HVAC_HEAT, - "cooling": CURRENT_HVAC_COOL, + "off": HVACAction.IDLE, + "heating": HVACAction.HEATING, + "cooling": HVACAction.COOLING, } PRESET_AWAY_AND_ECO = "Away and Eco" @@ -102,28 +94,30 @@ class NestThermostat(ClimateEntity): self._fan_modes = [FAN_ON, FAN_AUTO] # Set the default supported features - self._support_flags = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + self._support_flags = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) # Not all nest devices support cooling and heating remove unused self._operation_list = [] if self.device.can_heat and self.device.can_cool: - self._operation_list.append(HVAC_MODE_AUTO) - self._support_flags |= SUPPORT_TARGET_TEMPERATURE_RANGE + self._operation_list.append(HVACMode.AUTO) + self._support_flags |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE # Add supported nest thermostat features if self.device.can_heat: - self._operation_list.append(HVAC_MODE_HEAT) + self._operation_list.append(HVACMode.HEAT) if self.device.can_cool: - self._operation_list.append(HVAC_MODE_COOL) + self._operation_list.append(HVACMode.COOL) - self._operation_list.append(HVAC_MODE_OFF) + self._operation_list.append(HVACMode.OFF) # feature of device self._has_fan = self.device.has_fan if self._has_fan: - self._support_flags |= SUPPORT_FAN_MODE + self._support_flags |= ClimateEntityFeature.FAN_MODE # data attributes self._away = None @@ -195,7 +189,7 @@ class NestThermostat(ClimateEntity): return self._temperature @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" if self._mode == NEST_MODE_ECO: if self.device.previous_mode in MODE_NEST_TO_HASS: @@ -207,7 +201,7 @@ class NestThermostat(ClimateEntity): return MODE_NEST_TO_HASS[self._mode] @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return the current hvac action.""" return ACTION_NEST_TO_HASS[self._action] @@ -257,12 +251,12 @@ class NestThermostat(ClimateEntity): # restore target temperature self.schedule_update_ha_state(True) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set operation mode.""" self.device.mode = MODE_HASS_TO_NEST[hvac_mode] @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """List of available operation modes.""" return self._operation_list diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index f59a8e6ac31..ce0b68c782a 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -2,7 +2,7 @@ "domain": "nest", "name": "Nest", "config_flow": true, - "dependencies": ["ffmpeg", "http"], + "dependencies": ["ffmpeg", "http", "auth"], "after_dependencies": ["media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", "requirements": ["python-nest==4.2.0", "google-nest-sdm==1.8.0"], diff --git a/homeassistant/components/nest/translations/hu.json b/homeassistant/components/nest/translations/hu.json index 2bb9d2dbaec..1f98e162a7b 100644 --- a/homeassistant/components/nest/translations/hu.json +++ b/homeassistant/components/nest/translations/hu.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." @@ -33,7 +33,7 @@ "data": { "flow_impl": "Szolg\u00e1ltat\u00f3" }, - "description": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert", + "description": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert", "title": "Hiteles\u00edt\u00e9si Szolg\u00e1ltat\u00f3" }, "link": { @@ -44,7 +44,7 @@ "title": "Nest fi\u00f3k \u00f6sszekapcsol\u00e1sa" }, "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "pubsub": { "data": { @@ -54,7 +54,7 @@ "title": "Google Cloud konfigur\u00e1l\u00e1sa" }, "reauth_confirm": { - "description": "A Nest integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kodat", + "description": "A Nest integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kj\u00e1t", "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 7c92631351e..5942414bcf3 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "invalid_access_token": "Token di accesso non valido", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", diff --git a/homeassistant/components/nest/translations/pl.json b/homeassistant/components/nest/translations/pl.json index c81e5f180b9..5e9b9895db4 100644 --- a/homeassistant/components/nest/translations/pl.json +++ b/homeassistant/components/nest/translations/pl.json @@ -27,7 +27,7 @@ "code": "Token dost\u0119pu" }, "description": "Aby po\u0142\u0105czy\u0107 swoje konto Google, [authorize your account]({url}). \n\nPo autoryzacji skopiuj i wklej podany poni\u017cej token uwierzytelniaj\u0105cy.", - "title": "Po\u0142\u0105cz z kontem Google" + "title": "Po\u0142\u0105czenie z kontem Google" }, "init": { "data": { diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 7fa9fe02956..e5eaad50c9f 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -8,7 +8,7 @@ import aiohttp import pyatmo import voluptuous as vol -from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError, PlatformNotReady @@ -112,6 +112,8 @@ async def async_setup_entry( class NetatmoCamera(NetatmoBase, Camera): """Representation of a Netatmo camera.""" + _attr_supported_features = CameraEntityFeature.STREAM + def __init__( self, data_handler: NetatmoDataHandler, @@ -217,11 +219,6 @@ class NetatmoCamera(NetatmoBase, Camera): """Return True if entity is available.""" return bool(self._alim_status == "on" or self._status == "disconnected") - @property - def supported_features(self) -> int: - """Return supported features.""" - return SUPPORT_STREAM - @property def brand(self) -> str: """Return the camera brand.""" diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 623b7d0573a..cc515e50a11 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -9,16 +9,12 @@ import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, DEFAULT_MIN_TEMP, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -69,8 +65,6 @@ PRESET_FROST_GUARD = "Frost Guard" PRESET_SCHEDULE = "Schedule" PRESET_MANUAL = "Manual" -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE -SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] SUPPORT_PRESET = [PRESET_AWAY, PRESET_BOOST, PRESET_FROST_GUARD, PRESET_SCHEDULE] STATE_NETATMO_SCHEDULE = "schedule" @@ -100,17 +94,17 @@ NETATMO_MAP_PRESET = { } HVAC_MAP_NETATMO = { - PRESET_SCHEDULE: HVAC_MODE_AUTO, - STATE_NETATMO_HG: HVAC_MODE_AUTO, - PRESET_FROST_GUARD: HVAC_MODE_AUTO, - PRESET_BOOST: HVAC_MODE_HEAT, - STATE_NETATMO_OFF: HVAC_MODE_OFF, - STATE_NETATMO_MANUAL: HVAC_MODE_AUTO, - PRESET_MANUAL: HVAC_MODE_AUTO, - STATE_NETATMO_AWAY: HVAC_MODE_AUTO, + PRESET_SCHEDULE: HVACMode.AUTO, + STATE_NETATMO_HG: HVACMode.AUTO, + PRESET_FROST_GUARD: HVACMode.AUTO, + PRESET_BOOST: HVACMode.HEAT, + STATE_NETATMO_OFF: HVACMode.OFF, + STATE_NETATMO_MANUAL: HVACMode.AUTO, + PRESET_MANUAL: HVACMode.AUTO, + STATE_NETATMO_AWAY: HVACMode.AUTO, } -CURRENT_HVAC_MAP_NETATMO = {True: CURRENT_HVAC_HEAT, False: CURRENT_HVAC_IDLE} +CURRENT_HVAC_MAP_NETATMO = {True: HVACAction.HEATING, False: HVACAction.IDLE} DEFAULT_MAX_TEMP = 30 @@ -172,10 +166,12 @@ async def async_setup_entry( class NetatmoThermostat(NetatmoBase, ClimateEntity): """Representation a Netatmo thermostat.""" - _attr_hvac_mode = HVAC_MODE_AUTO + _attr_hvac_mode = HVACMode.AUTO _attr_max_temp = DEFAULT_MAX_TEMP _attr_preset_modes = SUPPORT_PRESET - _attr_supported_features = SUPPORT_FLAGS + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) _attr_target_temperature_step = PRECISION_HALVES _attr_temperature_unit = TEMP_CELSIUS @@ -223,9 +219,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self._boilerstatus: bool | None = None self._selected_schedule = None - self._attr_hvac_modes = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] + self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT] if self._model == NA_THERM: - self._attr_hvac_modes.append(HVAC_MODE_OFF) + self._attr_hvac_modes.append(HVACMode.OFF) self._attr_unique_id = f"{self._room.entity_id}-{self._model}" @@ -309,20 +305,20 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): and self._room.entity_id == room["id"] ): if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: - self._attr_hvac_mode = HVAC_MODE_OFF + self._attr_hvac_mode = HVACMode.OFF self._attr_preset_mode = STATE_NETATMO_OFF self._attr_target_temperature = 0 elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: - self._attr_hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_mode = HVACMode.HEAT self._attr_preset_mode = PRESET_MAP_NETATMO[PRESET_BOOST] self._attr_target_temperature = DEFAULT_MAX_TEMP elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: - self._attr_hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_mode = HVACMode.HEAT self._attr_target_temperature = room["therm_setpoint_temperature"] else: self._attr_target_temperature = room["therm_setpoint_temperature"] if self._attr_target_temperature == DEFAULT_MAX_TEMP: - self._attr_hvac_mode = HVAC_MODE_HEAT + self._attr_hvac_mode = HVACMode.HEAT self.async_write_ha_state() return @@ -335,7 +331,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): return @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction: """Return the current running hvac operation if supported.""" if self._model == NA_THERM and self._boilerstatus is not None: return CURRENT_HVAC_MAP_NETATMO[self._boilerstatus] @@ -343,23 +339,23 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): if ( heating_req := getattr(self._room, "heating_power_request", 0) ) is not None and heating_req > 0: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return HVACAction.HEATING + return HVACAction.IDLE - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_turn_off() - elif hvac_mode == HVAC_MODE_AUTO: - if self.hvac_mode == HVAC_MODE_OFF: + elif hvac_mode == HVACMode.AUTO: + if self.hvac_mode == HVACMode.OFF: await self.async_turn_on() await self.async_set_preset_mode(PRESET_SCHEDULE) - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: await self.async_set_preset_mode(PRESET_BOOST) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: await self.async_turn_on() if self.target_temperature == 0: @@ -371,7 +367,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): if ( preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX) and self._model == NA_VALVE - and self.hvac_mode == HVAC_MODE_HEAT + and self.hvac_mode == HVACMode.HEAT ): await self._climate_state.async_set_room_thermpoint( self._room.entity_id, @@ -387,7 +383,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): ) elif ( preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX) - and self.hvac_mode == HVAC_MODE_HEAT + and self.hvac_mode == HVACMode.HEAT ): await self._climate_state.async_set_room_thermpoint( self._room.entity_id, STATE_NETATMO_HOME @@ -423,7 +419,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): STATE_NETATMO_MANUAL, DEFAULT_MIN_TEMP, ) - elif self.hvac_mode != HVAC_MODE_OFF: + elif self.hvac_mode != HVACMode.OFF: await self._climate_state.async_set_room_thermpoint( self._room.entity_id, STATE_NETATMO_OFF ) diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 953ead88f33..565bd42594a 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/netatmo", "requirements": ["pyatmo==6.2.4"], "after_dependencies": ["cloud", "media_source"], - "dependencies": ["webhook"], + "dependencies": ["auth", "webhook"], "codeowners": ["@cgtobi"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index ae781d86ca8..51c46edbe86 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, @@ -12,7 +12,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "reauth_confirm": { "description": "A Netatmo integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kj\u00e1t.", diff --git a/homeassistant/components/netatmo/translations/it.json b/homeassistant/components/netatmo/translations/it.json index e26cd64705f..b2210a4375c 100644 --- a/homeassistant/components/netatmo/translations/it.json +++ b/homeassistant/components/netatmo/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." diff --git a/homeassistant/components/netatmo/translations/pt-BR.json b/homeassistant/components/netatmo/translations/pt-BR.json index 32cc610f596..b47c0ea3646 100644 --- a/homeassistant/components/netatmo/translations/pt-BR.json +++ b/homeassistant/components/netatmo/translations/pt-BR.json @@ -52,7 +52,7 @@ "lon_ne": "Longitude nordeste", "lon_sw": "Longitude sudoeste", "mode": "C\u00e1lculo", - "show_on_map": "Mostrar no mapa?" + "show_on_map": "Mostrar no mapa" }, "description": "Configure um sensor meteorol\u00f3gico p\u00fablico para uma \u00e1rea.", "title": "Sensor meteorol\u00f3gico p\u00fablico Netatmo" diff --git a/homeassistant/components/netgear/config_flow.py b/homeassistant/components/netgear/config_flow.py index 85c206ce463..79053c712fc 100644 --- a/homeassistant/components/netgear/config_flow.py +++ b/homeassistant/components/netgear/config_flow.py @@ -19,6 +19,7 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult +from homeassistant.util.network import is_ipv4_address from .const import ( CONF_CONSIDER_HOME, @@ -129,6 +130,9 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): hostname = cast(str, hostname) updated_data[CONF_HOST] = hostname + if not is_ipv4_address(str(hostname)): + return self.async_abort(reason="not_ipv4_address") + _LOGGER.debug("Netgear ssdp discovery info: %s", discovery_info) await self.async_set_unique_id(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]) diff --git a/homeassistant/components/netgear/manifest.json b/homeassistant/components/netgear/manifest.json index a5374f4c315..ae0824c82a5 100644 --- a/homeassistant/components/netgear/manifest.json +++ b/homeassistant/components/netgear/manifest.json @@ -2,7 +2,7 @@ "domain": "netgear", "name": "NETGEAR", "documentation": "https://www.home-assistant.io/integrations/netgear", - "requirements": ["pynetgear==0.9.4"], + "requirements": ["pynetgear==0.10.0"], "codeowners": ["@hacf-fr", "@Quentame", "@starkillerOG"], "iot_class": "local_polling", "config_flow": true, diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 7a46fd5719e..fc85f72e503 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -24,21 +24,9 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -81,7 +69,7 @@ SET_HVAC_RUN_MODE_SCHEMA = vol.All( { vol.Optional(ATTR_RUN_MODE): vol.In([HOLD_PERMANENT, HOLD_RESUME_SCHEDULE]), vol.Optional(ATTR_HVAC_MODE): vol.In( - [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO] + [HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO] ), } ), @@ -97,29 +85,29 @@ SET_HVAC_RUN_MODE_SCHEMA = vol.All( # # HA_TO_NEXIA_HVAC_MODE_MAP = { - HVAC_MODE_HEAT: OPERATION_MODE_HEAT, - HVAC_MODE_COOL: OPERATION_MODE_COOL, - HVAC_MODE_HEAT_COOL: OPERATION_MODE_AUTO, - HVAC_MODE_AUTO: OPERATION_MODE_AUTO, - HVAC_MODE_OFF: OPERATION_MODE_OFF, + HVACMode.HEAT: OPERATION_MODE_HEAT, + HVACMode.COOL: OPERATION_MODE_COOL, + HVACMode.HEAT_COOL: OPERATION_MODE_AUTO, + HVACMode.AUTO: OPERATION_MODE_AUTO, + HVACMode.OFF: OPERATION_MODE_OFF, } NEXIA_TO_HA_HVAC_MODE_MAP = { value: key for key, value in HA_TO_NEXIA_HVAC_MODE_MAP.items() } HVAC_MODES = [ - HVAC_MODE_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_COOL, + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.HEAT_COOL, + HVACMode.HEAT, + HVACMode.COOL, ] NEXIA_SUPPORTED = ( - SUPPORT_TARGET_TEMPERATURE_RANGE - | SUPPORT_TARGET_TEMPERATURE - | SUPPORT_FAN_MODE - | SUPPORT_PRESET_MODE + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + | ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE ) @@ -177,9 +165,9 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): self._has_dehumidify_support = self._thermostat.has_dehumidify_support() supported = NEXIA_SUPPORTED if self._has_humidify_support or self._has_dehumidify_support: - supported |= SUPPORT_TARGET_HUMIDITY + supported |= ClimateEntityFeature.TARGET_HUMIDITY if self._has_emergency_heat: - supported |= SUPPORT_AUX_HEAT + supported |= ClimateEntityFeature.AUX_HEAT self._attr_supported_features = supported self._attr_preset_modes = self._zone.get_presets() self._attr_fan_modes = self._thermostat.get_fan_modes() @@ -281,25 +269,25 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): return self._zone.get_heating_setpoint() @property - def hvac_action(self) -> str: + def hvac_action(self) -> HVACAction: """Operation ie. heat, cool, idle.""" system_status = self._thermostat.get_system_status() zone_called = self._zone.is_calling() if self._zone.get_requested_mode() == OPERATION_MODE_OFF: - return CURRENT_HVAC_OFF + return HVACAction.OFF if not zone_called: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if system_status == SYSTEM_STATUS_COOL: - return CURRENT_HVAC_COOL + return HVACAction.COOLING if system_status == SYSTEM_STATUS_HEAT: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if system_status == SYSTEM_STATUS_IDLE: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_IDLE + return HVACAction.IDLE + return HVACAction.IDLE @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current mode, as the user-visible name.""" mode = self._zone.get_requested_mode() hold = self._zone.is_in_permanent_hold() @@ -310,7 +298,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): # heating and cooling to the # temp range. if hold and mode == OPERATION_MODE_AUTO: - return HVAC_MODE_HEAT_COOL + return HVACMode.HEAT_COOL return NEXIA_TO_HA_HVAC_MODE_MAP[mode] @@ -412,9 +400,9 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): self.set_hvac_mode(OPERATION_MODE_AUTO) self._signal_zone_update() - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the system mode (Auto, Heat_Cool, Cool, Heat, etc).""" - if hvac_mode == HVAC_MODE_AUTO: + if hvac_mode == HVACMode.AUTO: self._zone.call_return_to_schedule() self._zone.set_mode(mode=OPERATION_MODE_AUTO) else: diff --git a/homeassistant/components/nfandroidtv/translations/ca.json b/homeassistant/components/nfandroidtv/translations/ca.json index 861ad41a39b..0eda4938d9d 100644 --- a/homeassistant/components/nfandroidtv/translations/ca.json +++ b/homeassistant/components/nfandroidtv/translations/ca.json @@ -13,7 +13,7 @@ "host": "Amfitri\u00f3", "name": "Nom" }, - "description": "Aquesta integraci\u00f3 necessita l'aplicaci\u00f3 Notificacions per a Android TV. \n\nPer Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nPer Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\nHauries de configurar o b\u00e9 una reserva DHCP al router (consulta el manual del teu rounter) o b\u00e9 adre\u00e7a IP est\u00e0tica al dispositiu. Si no o fas, el disositiu acabar\u00e0 deixant d'estar disponible.", + "description": "Consulta la documentaci\u00f3 per assegurar-te que compleixes tots els requisits.", "title": "Notificacions per a Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/de.json b/homeassistant/components/nfandroidtv/translations/de.json index b3adce9ac07..d9df9058d13 100644 --- a/homeassistant/components/nfandroidtv/translations/de.json +++ b/homeassistant/components/nfandroidtv/translations/de.json @@ -13,7 +13,7 @@ "host": "Host", "name": "Name" }, - "description": "Diese Integration erfordert die App \"Benachrichtigungen f\u00fcr Android TV\".\n\nF\u00fcr Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nF\u00fcr Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nDu solltest entweder eine DHCP-Reservierung auf deinem Router (siehe Benutzerhandbuch deines Routers) oder eine statische IP-Adresse auf dem Ger\u00e4t einrichten. Andernfalls wird das Ger\u00e4t irgendwann nicht mehr verf\u00fcgbar sein.", + "description": "Bitte lies die Dokumentation, um sicherzustellen, dass alle Anforderungen erf\u00fcllt sind.", "title": "Benachrichtigungen f\u00fcr Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/en.json b/homeassistant/components/nfandroidtv/translations/en.json index f117428df35..3c1383b91b8 100644 --- a/homeassistant/components/nfandroidtv/translations/en.json +++ b/homeassistant/components/nfandroidtv/translations/en.json @@ -13,7 +13,7 @@ "host": "Host", "name": "Name" }, - "description": "This integration requires the Notifications for Android TV app.\n\nFor Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nFor Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nYou should set up either DHCP reservation on your router (refer to your router's user manual) or a static IP address on the device. If not, the device will eventually become unavailable.", + "description": "Please refer to the documentation to make sure all requirements are met.", "title": "Notifications for Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/et.json b/homeassistant/components/nfandroidtv/translations/et.json index f2405ab1421..f567795b608 100644 --- a/homeassistant/components/nfandroidtv/translations/et.json +++ b/homeassistant/components/nfandroidtv/translations/et.json @@ -13,7 +13,7 @@ "host": "Host", "name": "Nimi" }, - "description": "See sidumine n\u00f5uab Android TV rakenduse Notifications for Android TV kasutamist.\n\nAndroid TV jaoks: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nFire TV jaoks: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nPead seadma ruuterile kas DHCP-reservatsiooni (vt ruuteri kasutusjuhendit) v\u00f5i seadme staatilise IP-aadressi. Vastasel juhul muutub seade l\u00f5puks k\u00e4ttesaamatuks.", + "description": "Vaata dokumentatsiooni, et veenduda, et k\u00f5ik n\u00f5uded on t\u00e4idetud.", "title": "Android TV / Fire TV teavitused" } } diff --git a/homeassistant/components/nfandroidtv/translations/fr.json b/homeassistant/components/nfandroidtv/translations/fr.json index 6d00852889b..7c69bbc6ac0 100644 --- a/homeassistant/components/nfandroidtv/translations/fr.json +++ b/homeassistant/components/nfandroidtv/translations/fr.json @@ -13,7 +13,7 @@ "host": "H\u00f4te", "name": "Nom" }, - "description": "Cette int\u00e9gration n\u00e9cessite l'application Notifications pour Android TV. \n\nPour Android TV\u00a0: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nPour Fire TV\u00a0: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\nVous devez configurer soit une r\u00e9servation DHCP sur votre routeur (reportez-vous au manuel d'utilisation de votre routeur) soit une adresse IP statique sur l'appareil. Sinon, l'appareil finira par devenir indisponible.", + "description": "Veuillez consulter la documentation afin de vous assurer que toutes les conditions sont respect\u00e9es.", "title": "Notifications pour Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/he.json b/homeassistant/components/nfandroidtv/translations/he.json index dc4d71c70ca..65eefcd05b4 100644 --- a/homeassistant/components/nfandroidtv/translations/he.json +++ b/homeassistant/components/nfandroidtv/translations/he.json @@ -13,7 +13,7 @@ "host": "\u05de\u05d0\u05e8\u05d7", "name": "\u05e9\u05dd" }, - "description": "\u05e9\u05d9\u05dc\u05d5\u05d1 \u05d6\u05d4 \u05d3\u05d5\u05e8\u05e9 \u05d0\u05ea \u05d4\u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05d9\u05d9\u05e9\u05d5\u05dd \u05d4\u05ea\u05e8\u05d0\u05d5\u05ea \u05e2\u05d1\u05d5\u05e8 \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05ea \u05d0\u05e0\u05d3\u05e8\u05d5\u05d0\u05d9\u05d3.\n\n\u05e2\u05d1\u05d5\u05e8 \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d9\u05ea \u05d0\u05e0\u05d3\u05e8\u05d5\u05d0\u05d9\u05d3: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n\u05e2\u05d1\u05d5\u05d3 \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05ea \u05d0\u05de\u05d6\u05d5\u05df: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\n\u05e2\u05dc\u05d9\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d4\u05d6\u05de\u05e0\u05ea DHCP \u05d1\u05e0\u05ea\u05d1 \u05e9\u05dc\u05da (\u05e2\u05d9\u05d9\u05df \u05d1\u05de\u05d3\u05e8\u05d9\u05da \u05dc\u05de\u05e9\u05ea\u05de\u05e9 \u05e9\u05dc \u05d4\u05e0\u05ea\u05d1) \u05d0\u05d5 \u05db\u05ea\u05d5\u05d1\u05ea IP \u05e1\u05d8\u05d8\u05d9\u05ea \u05d1\u05de\u05db\u05e9\u05d9\u05e8. \u05d0\u05dd \u05dc\u05d0, \u05d4\u05d4\u05ea\u05e7\u05df \u05d9\u05d4\u05e4\u05d5\u05da \u05d1\u05e1\u05d5\u05e4\u05d5 \u05e9\u05dc \u05d3\u05d1\u05e8 \u05dc\u05dc\u05d0 \u05d6\u05de\u05d9\u05df.", + "description": "\u05e0\u05d0 \u05dc\u05e2\u05d9\u05d9\u05df \u05d1\u05ea\u05d9\u05e2\u05d5\u05d3 \u05db\u05d3\u05d9 \u05dc\u05d5\u05d5\u05d3\u05d0 \u05e9\u05db\u05dc \u05d4\u05d3\u05e8\u05d9\u05e9\u05d5\u05ea \u05de\u05ea\u05e7\u05d9\u05d9\u05de\u05d5\u05ea.", "title": "\u05d4\u05ea\u05e8\u05d0\u05d5\u05ea \u05e2\u05d1\u05d5\u05e8 \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d9\u05ea \u05d0\u05e0\u05d3\u05e8\u05d5\u05d0\u05d9\u05d3 / \u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d9\u05ea \u05d0\u05de\u05d6\u05d5\u05df" } } diff --git a/homeassistant/components/nfandroidtv/translations/hu.json b/homeassistant/components/nfandroidtv/translations/hu.json index c0dc8d679d6..0c8d0567483 100644 --- a/homeassistant/components/nfandroidtv/translations/hu.json +++ b/homeassistant/components/nfandroidtv/translations/hu.json @@ -11,9 +11,9 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, - "description": "Ehhez az integr\u00e1ci\u00f3hoz az \u00c9rtes\u00edt\u00e9sek az Android TV alkalmaz\u00e1shoz sz\u00fcks\u00e9ges. \n\nAndroid TV eset\u00e9n: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nA Fire TV eset\u00e9ben: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\nBe kell \u00e1ll\u00edtania a DHCP -foglal\u00e1st az \u00fatv\u00e1laszt\u00f3n (l\u00e1sd az \u00fatv\u00e1laszt\u00f3 felhaszn\u00e1l\u00f3i k\u00e9zik\u00f6nyv\u00e9t), vagy egy statikus IP -c\u00edmet az eszk\u00f6z\u00f6n. Ha nem, az eszk\u00f6z v\u00e9g\u00fcl el\u00e9rhetetlenn\u00e9 v\u00e1lik.", + "description": "K\u00e9rj\u00fck, olvassa el a dokument\u00e1ci\u00f3t, hogy megbizonyosodjon arr\u00f3l, hogy minden k\u00f6vetelm\u00e9ny teljes\u00fcl.", "title": "\u00c9rtes\u00edt\u00e9sek Android TV / Fire TV eset\u00e9n" } } diff --git a/homeassistant/components/nfandroidtv/translations/id.json b/homeassistant/components/nfandroidtv/translations/id.json index fd70679d5a4..de4ded1e98a 100644 --- a/homeassistant/components/nfandroidtv/translations/id.json +++ b/homeassistant/components/nfandroidtv/translations/id.json @@ -13,7 +13,7 @@ "host": "Host", "name": "Nama" }, - "description": "Integrasi ini memerlukan aplikasi Notifikasi untuk Android TV.\n\nUntuk Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nUntuk Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nAnda harus mengatur reservasi DHCP di router Anda (lihat manual pengguna router Anda) atau alamat IP statis pada perangkat. Jika tidak, perangkat akhirnya akan menjadi tidak tersedia.", + "description": "Rujuk ke dokumentasi untuk memastikan semua persyaratan terpenuhi.", "title": "Notifikasi untuk Android TV/Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/it.json b/homeassistant/components/nfandroidtv/translations/it.json index 6251a31e3bc..83eea49b419 100644 --- a/homeassistant/components/nfandroidtv/translations/it.json +++ b/homeassistant/components/nfandroidtv/translations/it.json @@ -13,7 +13,7 @@ "host": "Host", "name": "Nome" }, - "description": "Questa integrazione richiede l'app Notifiche per Android TV. \n\nPer Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nPer Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\nDovresti configurare la prenotazione DHCP sul router (fai riferimento al manuale utente del router) o un indirizzo IP statico sul dispositivo. In caso contrario, il dispositivo alla fine non sar\u00e0 pi\u00f9 disponibile.", + "description": "Fai riferimento alla documentazione per assicurarti che tutti i requisiti siano soddisfatti.", "title": "Notifiche per Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/nl.json b/homeassistant/components/nfandroidtv/translations/nl.json index acd936abe70..10c9f44a94a 100644 --- a/homeassistant/components/nfandroidtv/translations/nl.json +++ b/homeassistant/components/nfandroidtv/translations/nl.json @@ -13,7 +13,7 @@ "host": "Host", "name": "Naam" }, - "description": "Voor deze integratie is de app Notifications for Android TV vereist.\n\nVoor Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nVoor Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nU moet een DHCP-reservering op uw router instellen (raadpleeg de gebruikershandleiding van uw router) of een statisch IP-adres op het apparaat instellen. Zo niet, dan zal het apparaat uiteindelijk onbeschikbaar worden.", + "description": "Raadpleeg de documentatie om er zeker van te zijn dat aan alle vereisten is voldaan.", "title": "Meldingen voor Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/no.json b/homeassistant/components/nfandroidtv/translations/no.json index e8aea574c96..8d59ca40b0d 100644 --- a/homeassistant/components/nfandroidtv/translations/no.json +++ b/homeassistant/components/nfandroidtv/translations/no.json @@ -13,7 +13,7 @@ "host": "Vert", "name": "Navn" }, - "description": "Denne integrasjonen krever Notifications for Android TV -appen. \n\n For Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n For Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n Du b\u00f8r konfigurere enten DHCP -reservasjon p\u00e5 ruteren din (se brukerh\u00e5ndboken til ruteren din) eller en statisk IP -adresse p\u00e5 enheten. Hvis ikke, vil enheten til slutt bli utilgjengelig.", + "description": "Se dokumentasjonen for \u00e5 sikre at alle krav er oppfylt.", "title": "Varsler for Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/pl.json b/homeassistant/components/nfandroidtv/translations/pl.json index 4dd742b7c1f..a42335ceb10 100644 --- a/homeassistant/components/nfandroidtv/translations/pl.json +++ b/homeassistant/components/nfandroidtv/translations/pl.json @@ -13,7 +13,7 @@ "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, - "description": "Ta integracja wymaga aplikacji Powiadomienia dla Androida TV. \n\nAndroid TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nFire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\nNale\u017cy skonfigurowa\u0107 rezerwacj\u0119 DHCP na routerze (patrz instrukcja obs\u0142ugi routera) lub ustawi\u0107 statyczny adres IP na urz\u0105dzeniu. Je\u015bli tego nie zrobisz, urz\u0105dzenie ostatecznie stanie si\u0119 niedost\u0119pne.", + "description": "Zapoznaj si\u0119 z dokumentacj\u0105, aby upewni\u0107 si\u0119, \u017ce wszystkie wymagania s\u0105 spe\u0142nione.", "title": "Powiadomienia dla Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/pt-BR.json b/homeassistant/components/nfandroidtv/translations/pt-BR.json index 43b7a296c21..f4488408b42 100644 --- a/homeassistant/components/nfandroidtv/translations/pt-BR.json +++ b/homeassistant/components/nfandroidtv/translations/pt-BR.json @@ -13,7 +13,7 @@ "host": "Nome do host", "name": "Nome" }, - "description": "Essa integra\u00e7\u00e3o requer as Notifica\u00e7\u00f5es para o aplicativo Android TV.\n\nPara Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nPara Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\nVoc\u00ea deve configurar a reserva DHCP no roteador (consulte o manual do usu\u00e1rio do roteador) ou um endere\u00e7o IP est\u00e1tico no dispositivo. Se n\u00e3o, o dispositivo acabar\u00e1 por ficar indispon\u00edvel.", + "description": "Consulte a documenta\u00e7\u00e3o para garantir que todos os requisitos sejam atendidos.", "title": "Notifica\u00e7\u00f5es para Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/ru.json b/homeassistant/components/nfandroidtv/translations/ru.json index ce0d4651dfc..12451d4735b 100644 --- a/homeassistant/components/nfandroidtv/translations/ru.json +++ b/homeassistant/components/nfandroidtv/translations/ru.json @@ -13,7 +13,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u0414\u043b\u044f \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \"Notifications for Android TV\". \n\n\u0414\u043b\u044f Android TV: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n\u0414\u043b\u044f Fire TV: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0440\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 DHCP \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0435 (\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0443) \u0438\u043b\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 IP-\u0430\u0434\u0440\u0435\u0441 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c.", + "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0432\u0441\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u043e\u0431\u043b\u044e\u0434\u0435\u043d\u044b.", "title": "Notifications for Android TV / Fire TV" } } diff --git a/homeassistant/components/nfandroidtv/translations/tr.json b/homeassistant/components/nfandroidtv/translations/tr.json index b894cf83687..835e61eea66 100644 --- a/homeassistant/components/nfandroidtv/translations/tr.json +++ b/homeassistant/components/nfandroidtv/translations/tr.json @@ -13,7 +13,7 @@ "host": "Sunucu", "name": "Ad" }, - "description": "Bu entegrasyon, Android TV i\u00e7in Bildirimler uygulamas\u0131n\u0131 gerektirir. \n\n Android TV i\u00e7in: https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\n Fire TV i\u00e7in: https://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK \n\n Y\u00f6nlendiricinizde DHCP rezervasyonu (y\u00f6nlendiricinizin kullan\u0131m k\u0131lavuzuna bak\u0131n) veya cihazda statik bir IP adresi ayarlamal\u0131s\u0131n\u0131z. Aksi takdirde, cihaz sonunda kullan\u0131lamaz hale gelecektir.", + "description": "T\u00fcm gereksinimlerin kar\u015f\u0131land\u0131\u011f\u0131ndan emin olmak i\u00e7in l\u00fctfen belgelere bak\u0131n.", "title": "Android TV / Fire TV i\u00e7in Bildirimler" } } diff --git a/homeassistant/components/nfandroidtv/translations/zh-Hant.json b/homeassistant/components/nfandroidtv/translations/zh-Hant.json index b16d55a44bd..755ccdfeec8 100644 --- a/homeassistant/components/nfandroidtv/translations/zh-Hant.json +++ b/homeassistant/components/nfandroidtv/translations/zh-Hant.json @@ -13,7 +13,7 @@ "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u6b64\u6574\u5408\u9700\u8981\u5b89\u88dd Notifications for Android TV App\u3002\n\nAndroid TV \u7248\u672c\uff1ahttps://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google\nFire TV \u7248\u672c\uff1ahttps://www.amazon.com/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK\n\n\u8acb\u65bc\u8def\u7531\u5668\uff08\u8acb\u53c3\u8003\u8def\u7531\u5668\u624b\u518a\uff09\u4e2d\u8a2d\u5b9a\u4fdd\u7559\u88dd\u7f6e DHCP IP \u6216\u975c\u614b IP\u3002\u5047\u5982\u672a\u9032\u884c\u6b64\u8a2d\u5b9a\uff0c\u88dd\u7f6e\u53ef\u80fd\u6703\u8b8a\u6210\u4e0d\u53ef\u7528\u3002", + "description": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u6240\u6709\u5fc5\u8981\u9700\u6c42\u3002", "title": "Android TV / Fire TV \u901a\u77e5" } } diff --git a/homeassistant/components/nina/translations/zh-Hant.json b/homeassistant/components/nina/translations/zh-Hant.json index 0ba4436722d..212c65070d8 100644 --- a/homeassistant/components/nina/translations/zh-Hant.json +++ b/homeassistant/components/nina/translations/zh-Hant.json @@ -5,22 +5,22 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "no_selection": "\u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u57ce\u5e02/\u570b\u5bb6", + "no_selection": "\u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u57ce\u5e02/\u7e23\u5e02", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "user": { "data": { - "_a_to_d": "\u57ce\u5e02/\u570b\u5bb6\uff08A-D\uff09", - "_e_to_h": "\u57ce\u5e02/\u570b\u5bb6\uff08E-H\uff09", - "_i_to_l": "\u57ce\u5e02/\u570b\u5bb6\uff08I-L\uff09", - "_m_to_q": "\u57ce\u5e02/\u570b\u5bb6\uff08M-Q\uff09", - "_r_to_u": "\u57ce\u5e02/\u570b\u5bb6\uff08R-U\uff09", - "_v_to_z": "\u57ce\u5e02/\u570b\u5bb6\uff08V-Z\uff09", + "_a_to_d": "\u57ce\u5e02/\u7e23\u5e02\uff08A-D\uff09", + "_e_to_h": "\u57ce\u5e02/\u7e23\u5e02\uff08E-H\uff09", + "_i_to_l": "\u57ce\u5e02/\u7e23\u5e02\uff08I-L\uff09", + "_m_to_q": "\u57ce\u5e02/\u7e23\u5e02\uff08M-Q\uff09", + "_r_to_u": "\u57ce\u5e02/\u7e23\u5e02\uff08R-U\uff09", + "_v_to_z": "\u57ce\u5e02/\u7e23\u5e02\uff08V-Z\uff09", "corona_filter": "\u79fb\u9664 Corona \u8b66\u544a", - "slots": "\u6bcf\u500b\u57ce\u5e02/\u570b\u5bb6\u6700\u5927\u8b66\u544a\u503c" + "slots": "\u6bcf\u500b\u57ce\u5e02/\u7e23\u5e02\u6700\u5927\u8b66\u544a\u503c" }, - "title": "\u9078\u64c7\u57ce\u5e02/\u570b\u5bb6" + "title": "\u9078\u64c7\u57ce\u5e02/\u7e23\u5e02" } } } diff --git a/homeassistant/components/nmap_tracker/translations/en.json b/homeassistant/components/nmap_tracker/translations/en.json index ae4175e0f14..ee615b87e91 100644 --- a/homeassistant/components/nmap_tracker/translations/en.json +++ b/homeassistant/components/nmap_tracker/translations/en.json @@ -30,7 +30,8 @@ "home_interval": "Minimum number of minutes between scans of active devices (preserve battery)", "hosts": "Network addresses (comma separated) to scan", "interval_seconds": "Scan interval", - "scan_options": "Raw configurable scan options for Nmap" + "scan_options": "Raw configurable scan options for Nmap", + "track_new_devices": "Track new devices" }, "description": "Configure hosts to be scanned by Nmap. Network address and excludes can be IP Addresses (192.168.1.1), IP Networks (192.168.0.0/24) or IP Ranges (192.168.1.0-32)." } diff --git a/homeassistant/components/nmap_tracker/translations/et.json b/homeassistant/components/nmap_tracker/translations/et.json index 538f1127448..ac23cb7004f 100644 --- a/homeassistant/components/nmap_tracker/translations/et.json +++ b/homeassistant/components/nmap_tracker/translations/et.json @@ -9,9 +9,9 @@ "step": { "user": { "data": { - "exclude": "V\u00f5rguaadressid (komadega eraldatud), mis tuleb skaneerimisest v\u00e4lja j\u00e4tta", + "exclude": "V\u00f5rguaadressid (komadega eraldatud) mis tuleb skaneerimisest v\u00e4lja j\u00e4tta", "home_interval": "Minimaalne minutite arv aktiivsete seadmete skaneerimise vahel (aku s\u00e4ilitamine)", - "hosts": "Skaneeritavad v\u00f5rgu aadressid (komadega eraldatud)", + "hosts": "V\u00f5rgu aadressid (komadega eraldatud)", "scan_options": "Nmapi algseadistavad skaneerimisvalikud" }, "description": "Konfigureeri hostid, mida Nmap skannib. V\u00f5rguaadress ja v\u00e4ljaj\u00e4etud v\u00f5ivad olla IP-aadressid (192.168.1.1), IP-v\u00f5rgud (192.168.0.0/24) v\u00f5i IP-vahemikud (192.168.1.0-32)." diff --git a/homeassistant/components/nmap_tracker/translations/hu.json b/homeassistant/components/nmap_tracker/translations/hu.json index 7385f12b3df..f54cf208e92 100644 --- a/homeassistant/components/nmap_tracker/translations/hu.json +++ b/homeassistant/components/nmap_tracker/translations/hu.json @@ -9,9 +9,9 @@ "step": { "user": { "data": { - "exclude": "H\u00e1l\u00f3zati c\u00edmek (vessz\u0151vel elv\u00e1lasztva), amelyeket kiz\u00e1r\u00e1sra ker\u00fclnek a vizsg\u00e1latb\u00f3l", + "exclude": "H\u00e1l\u00f3zati c\u00edmek (vessz\u0151vel elv\u00e1lasztva), amelyeket kiz\u00e1r\u00e1sra ker\u00fclnek a keres\u00e9sb\u0151l", "home_interval": "Minim\u00e1lis percsz\u00e1m az akt\u00edv eszk\u00f6z\u00f6k vizsg\u00e1lata k\u00f6z\u00f6tt (akkumul\u00e1tor k\u00edm\u00e9l\u00e9se)", - "hosts": "H\u00e1l\u00f3zati c\u00edmek (vessz\u0151vel elv\u00e1lasztva) a beolvas\u00e1shoz", + "hosts": "H\u00e1l\u00f3zati c\u00edmek (vessz\u0151vel elv\u00e1lasztva) a keres\u00e9shez", "scan_options": "Nyersen konfigur\u00e1lhat\u00f3 szkennel\u00e9si lehet\u0151s\u00e9gek az Nmap sz\u00e1m\u00e1ra" }, "description": "\u00c1ll\u00edtsa be a hogy milyen c\u00edmeket szkenneljen az Nmap. A h\u00e1l\u00f3zati c\u00edm lehet IP-c\u00edm (pl. 192.168.1.1), IP-h\u00e1l\u00f3zat (pl. 192.168.0.0/24) vagy IP-tartom\u00e1ny (pl. 192.168.1.0-32)." diff --git a/homeassistant/components/nmap_tracker/translations/no.json b/homeassistant/components/nmap_tracker/translations/no.json index acd25607c4f..1de32bed121 100644 --- a/homeassistant/components/nmap_tracker/translations/no.json +++ b/homeassistant/components/nmap_tracker/translations/no.json @@ -11,7 +11,7 @@ "data": { "exclude": "Nettverksadresser (kommaseparert) for \u00e5 ekskludere fra skanning", "home_interval": "Minimum antall minutter mellom skanninger av aktive enheter (lagre batteri)", - "hosts": "Nettverksadresser (kommaseparert) for \u00e5 skanne", + "hosts": "Nettverksadresser (atskilt med komma) som skal skannes", "scan_options": "R\u00e5 konfigurerbare skannealternativer for Nmap" }, "description": "Konfigurer verter som skal skannes av Nmap. Nettverksadresse og ekskluderer kan v\u00e6re IP-adresser (192.168.1.1), IP-nettverk (192.168.0.0/24) eller IP-omr\u00e5der (192.168.1.0-32)." @@ -28,7 +28,7 @@ "consider_home": "Sekunder \u00e5 vente til du merker en enhetssporing som ikke hjemme etter at den ikke er blitt sett.", "exclude": "Nettverksadresser (kommaseparert) for \u00e5 ekskludere fra skanning", "home_interval": "Minimum antall minutter mellom skanninger av aktive enheter (lagre batteri)", - "hosts": "Nettverksadresser (kommaseparert) for \u00e5 skanne", + "hosts": "Nettverksadresser (atskilt med komma) som skal skannes", "interval_seconds": "Skanneintervall", "scan_options": "R\u00e5 konfigurerbare skannealternativer for Nmap", "track_new_devices": "Spor nye enheter" diff --git a/homeassistant/components/nmap_tracker/translations/pt-BR.json b/homeassistant/components/nmap_tracker/translations/pt-BR.json index bf058495cb9..a80213ce5d6 100644 --- a/homeassistant/components/nmap_tracker/translations/pt-BR.json +++ b/homeassistant/components/nmap_tracker/translations/pt-BR.json @@ -9,9 +9,9 @@ "step": { "user": { "data": { - "exclude": "Endere\u00e7os de rede (separados por v\u00edrgula) para excluir do escaneamento", + "exclude": "Endere\u00e7os de rede (separados por v\u00edrgula) para excluir da verifica\u00e7\u00e3o", "home_interval": "N\u00famero m\u00ednimo de minutos entre escaneamento de dispositivos ativos (preservar bateria)", - "hosts": "Endere\u00e7os de rede (separados por v\u00edrgula) para escanear", + "hosts": "Endere\u00e7os de rede (separados por v\u00edrgula) para digitalizar", "scan_options": "Op\u00e7\u00f5es de escaneamento bruto configur\u00e1veis para Nmap" }, "description": "Configure os hosts a serem verificados pelo Nmap. O endere\u00e7o de rede e as exclus\u00f5es podem ser endere\u00e7os IP (192.168.1.1), redes IP (192.168.0.0/24) ou intervalos de IP (192.168.1.0-32)." @@ -26,14 +26,14 @@ "init": { "data": { "consider_home": "Segundos para esperar at\u00e9 marcar um rastreador de dispositivo como fora de casa depois de n\u00e3o ser visto.", - "exclude": "Endere\u00e7os de rede (separados por v\u00edrgula) para excluir do escaneamento", + "exclude": "Endere\u00e7os de rede (separados por v\u00edrgula) para excluir da verifica\u00e7\u00e3o", "home_interval": "N\u00famero m\u00ednimo de minutos entre escaneamento de dispositivos ativos (preservar bateria)", - "hosts": "Endere\u00e7os de rede (separados por v\u00edrgula) para escanear", + "hosts": "Endere\u00e7os de rede (separados por v\u00edrgula) para digitalizar", "interval_seconds": "Intervalo de varredura", - "scan_options": "Op\u00e7\u00f5es de varredura configur\u00e1veis brutas para Nmap", + "scan_options": "Op\u00e7\u00f5es de escaneamento bruto configur\u00e1veis para Nmap", "track_new_devices": "Rastrear novos dispositivos" }, - "description": "Configure hosts a serem digitalizados pelo Nmap. O endere\u00e7o de rede e exclus\u00f5es podem ser Endere\u00e7os IP (192.168.1.1), Redes IP (192.168.0.0/24) ou Faixas IP (192.168.1.0-32)." + "description": "Configure os hosts a serem verificados pelo Nmap. O endere\u00e7o de rede e as exclus\u00f5es podem ser endere\u00e7os IP (192.168.1.1), redes IP (192.168.0.0/24) ou intervalos de IP (192.168.1.0-32)." } } }, diff --git a/homeassistant/components/nmap_tracker/translations/tr.json b/homeassistant/components/nmap_tracker/translations/tr.json index 2d277e979b3..8e5dadb8100 100644 --- a/homeassistant/components/nmap_tracker/translations/tr.json +++ b/homeassistant/components/nmap_tracker/translations/tr.json @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "exclude": "Taraman\u0131n d\u0131\u015f\u0131nda tutulacak a\u011f adresleri (virg\u00fcl ayr\u0131lm\u0131\u015f)", + "exclude": "Tarama d\u0131\u015f\u0131nda b\u0131rak\u0131lacak a\u011f adresleri (virg\u00fclle ayr\u0131lm\u0131\u015f)", "home_interval": "Aktif cihazlar\u0131n taranmas\u0131 aras\u0131ndaki minimum dakika say\u0131s\u0131 (pilden tasarruf edin)", "hosts": "Taranacak a\u011f adresleri (virg\u00fclle ayr\u0131lm\u0131\u015f)", "scan_options": "Nmap i\u00e7in ham yap\u0131land\u0131r\u0131labilir tarama se\u00e7enekleri" @@ -26,7 +26,7 @@ "init": { "data": { "consider_home": "Bir cihaz izleyicisi g\u00f6r\u00fclmedikten sonra evde de\u011fil olarak i\u015faretlenene kadar beklemek i\u00e7in saniyeler kald\u0131.", - "exclude": "Taraman\u0131n d\u0131\u015f\u0131nda tutulacak a\u011f adresleri (virg\u00fcl ayr\u0131lm\u0131\u015f)", + "exclude": "Tarama d\u0131\u015f\u0131nda b\u0131rak\u0131lacak a\u011f adresleri (virg\u00fclle ayr\u0131lm\u0131\u015f)", "home_interval": "Aktif cihazlar\u0131n taranmas\u0131 aras\u0131ndaki minimum dakika say\u0131s\u0131 (pilden tasarruf edin)", "hosts": "Taranacak a\u011f adresleri (virg\u00fclle ayr\u0131lm\u0131\u015f)", "interval_seconds": "Tarama aral\u0131\u011f\u0131", diff --git a/homeassistant/components/no_ip/manifest.json b/homeassistant/components/no_ip/manifest.json index 565ef8a7840..a3e8faf873b 100644 --- a/homeassistant/components/no_ip/manifest.json +++ b/homeassistant/components/no_ip/manifest.json @@ -2,6 +2,6 @@ "domain": "no_ip", "name": "No-IP.com", "documentation": "https://www.home-assistant.io/integrations/no_ip", - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/notify/services.yaml b/homeassistant/components/notify/services.yaml index bc31aef1a6e..1a0de7344a3 100644 --- a/homeassistant/components/notify/services.yaml +++ b/homeassistant/components/notify/services.yaml @@ -36,7 +36,7 @@ notify: persistent_notification: name: Send a persistent notification - description: Sends a notification to the visible in the front-end. + description: Sends a notification that is visible in the front-end. fields: message: name: Message diff --git a/homeassistant/components/nuheat/climate.py b/homeassistant/components/nuheat/climate.py index 58af276b9a1..6cc70965ade 100644 --- a/homeassistant/components/nuheat/climate.py +++ b/homeassistant/components/nuheat/climate.py @@ -14,12 +14,9 @@ from nuheat.util import ( from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -45,7 +42,7 @@ _LOGGER = logging.getLogger(__name__) # The device does not have an off function. # To turn it off set to min_temp and PRESET_PERMANENT_HOLD -OPERATION_LIST = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] +OPERATION_LIST = [HVACMode.AUTO, HVACMode.HEAT] PRESET_RUN = "Run Schedule" PRESET_TEMPORARY_HOLD = "Temporary Hold" @@ -63,8 +60,6 @@ SCHEDULE_MODE_TO_PRESET_MODE_MAP = { value: key for key, value in PRESET_MODE_TO_SCHEDULE_MODE_MAP.items() } -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - async def async_setup_entry( hass: HomeAssistant, @@ -86,6 +81,11 @@ async def async_setup_entry( class NuHeatThermostat(CoordinatorEntity, ClimateEntity): """Representation of a NuHeat Thermostat.""" + _attr_hvac_modes = OPERATION_LIST + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, coordinator, thermostat, temperature_unit): """Initialize the thermostat.""" super().__init__(coordinator) @@ -99,11 +99,6 @@ class NuHeatThermostat(CoordinatorEntity, ClimateEntity): """Return the name of the thermostat.""" return self._thermostat.room - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - @property def temperature_unit(self): """Return the unit of measurement.""" @@ -130,24 +125,24 @@ class NuHeatThermostat(CoordinatorEntity, ClimateEntity): """Return the unique id.""" return self.coordinator.last_update_success and self._thermostat.online - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the system mode.""" - if hvac_mode == HVAC_MODE_AUTO: + if hvac_mode == HVACMode.AUTO: self._set_schedule_mode(SCHEDULE_RUN) - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: self._set_schedule_mode(SCHEDULE_HOLD) @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current setting heat or auto.""" if self._schedule_mode in (SCHEDULE_TEMPORARY_HOLD, SCHEDULE_HOLD): - return HVAC_MODE_HEAT - return HVAC_MODE_AUTO + return HVACMode.HEAT + return HVACMode.AUTO @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return current operation heat or idle.""" - return CURRENT_HVAC_HEAT if self._thermostat.heating else CURRENT_HVAC_IDLE + return HVACAction.HEATING if self._thermostat.heating else HVACAction.IDLE @property def min_temp(self): @@ -183,11 +178,6 @@ class NuHeatThermostat(CoordinatorEntity, ClimateEntity): """Return available preset modes.""" return PRESET_MODES - @property - def hvac_modes(self): - """Return list of possible operation modes.""" - return OPERATION_LIST - def set_preset_mode(self, preset_mode): """Update the hold mode of the thermostat.""" self._set_schedule_mode( @@ -223,7 +213,7 @@ class NuHeatThermostat(CoordinatorEntity, ClimateEntity): preset_mode, SCHEDULE_RUN ) elif self._schedule_mode == SCHEDULE_HOLD or ( - hvac_mode and hvac_mode == HVAC_MODE_HEAT + hvac_mode and hvac_mode == HVACMode.HEAT ): target_schedule_mode = SCHEDULE_HOLD diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index e999c3911db..6a39bea8cb6 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from pynuki.constants import MODE_OPENER_CONTINUOUS import voluptuous as vol -from homeassistant.components.lock import SUPPORT_OPEN, LockEntity +from homeassistant.components.lock import LockEntity, LockEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform @@ -60,6 +60,8 @@ async def async_setup_entry( class NukiDeviceEntity(NukiEntity, LockEntity, ABC): """Representation of a Nuki device.""" + _attr_supported_features = LockEntityFeature.OPEN + @property def name(self): """Return the name of the lock.""" @@ -84,11 +86,6 @@ class NukiDeviceEntity(NukiEntity, LockEntity, ABC): } return data - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_OPEN - @property def available(self) -> bool: """Return True if entity is available.""" diff --git a/homeassistant/components/numato/__init__.py b/homeassistant/components/numato/__init__.py index e25183a7cff..e2a5400e050 100644 --- a/homeassistant/components/numato/__init__.py +++ b/homeassistant/components/numato/__init__.py @@ -168,7 +168,6 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -# pylint: disable=no-self-use class NumatoAPI: """Home-Assistant specific API for numato device access.""" diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index 091bdaa5736..67ad9255c68 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -3,7 +3,7 @@ "name": "National Weather Service (NWS)", "documentation": "https://www.home-assistant.io/integrations/nws", "codeowners": ["@MatthewFlamm"], - "requirements": ["pynws==1.3.2"], + "requirements": ["pynws==1.4.1"], "quality_scale": "platinum", "config_flow": true, "iot_class": "cloud_polling", diff --git a/homeassistant/components/nx584/alarm_control_panel.py b/homeassistant/components/nx584/alarm_control_panel.py index 7e49b163d71..2ef664cb6d4 100644 --- a/homeassistant/components/nx584/alarm_control_panel.py +++ b/homeassistant/components/nx584/alarm_control_panel.py @@ -11,10 +11,7 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import ( PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, + AlarmControlPanelEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -95,6 +92,11 @@ async def async_setup_platform( class NX584Alarm(alarm.AlarmControlPanelEntity): """Representation of a NX584-based alarm panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + def __init__(self, name, alarm_client, url): """Init the nx584 alarm panel.""" self._name = name @@ -110,18 +112,13 @@ class NX584Alarm(alarm.AlarmControlPanelEntity): @property def code_format(self): """Return one or more digits/characters.""" - return alarm.FORMAT_NUMBER + return alarm.CodeFormat.NUMBER @property def state(self): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - def update(self): """Process new events from panel.""" try: diff --git a/homeassistant/components/nzbget/translations/hu.json b/homeassistant/components/nzbget/translations/hu.json index 6db44f83c28..928d054707b 100644 --- a/homeassistant/components/nzbget/translations/hu.json +++ b/homeassistant/components/nzbget/translations/hu.json @@ -12,7 +12,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "port": "Port", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 4efc094c297..ad493022dfc 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -187,7 +187,9 @@ class OctoPrintEstimatedFinishTimeSensor(OctoPrintSensorBase): read_time = self.coordinator.data["last_read_time"] - return read_time + timedelta(seconds=job.progress.print_time_left) + return (read_time + timedelta(seconds=job.progress.print_time_left)).replace( + second=0 + ) class OctoPrintStartTimeSensor(OctoPrintSensorBase): @@ -215,7 +217,9 @@ class OctoPrintStartTimeSensor(OctoPrintSensorBase): read_time = self.coordinator.data["last_read_time"] - return read_time - timedelta(seconds=job.progress.print_time) + return (read_time - timedelta(seconds=job.progress.print_time)).replace( + second=0 + ) class OctoPrintTemperatureSensor(OctoPrintSensorBase): diff --git a/homeassistant/components/oem/climate.py b/homeassistant/components/oem/climate.py index d5b4ffe5d71..846113fb586 100644 --- a/homeassistant/components/oem/climate.py +++ b/homeassistant/components/oem/climate.py @@ -7,13 +7,9 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -39,8 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] +SUPPORT_HVAC = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF] def setup_platform( @@ -67,6 +62,9 @@ def setup_platform( class ThermostatDevice(ClimateEntity): """Interface class for the oemthermostat module.""" + _attr_hvac_modes = SUPPORT_HVAC + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__(self, thermostat, name): """Initialize the device.""" self._name = name @@ -79,29 +77,16 @@ class ThermostatDevice(ClimateEntity): self._mode = None @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - - @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ if self._mode == 2: - return HVAC_MODE_HEAT + return HVACMode.HEAT if self._mode == 1: - return HVAC_MODE_AUTO - return HVAC_MODE_OFF - - @property - def hvac_modes(self): - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ - return SUPPORT_HVAC + return HVACMode.AUTO + return HVACMode.OFF @property def name(self): @@ -114,13 +99,13 @@ class ThermostatDevice(ClimateEntity): return TEMP_CELSIUS @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return current hvac i.e. heat, cool, idle.""" if not self._mode: - return CURRENT_HVAC_OFF + return HVACAction.OFF if self._state: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return HVACAction.HEATING + return HVACAction.IDLE @property def current_temperature(self): @@ -132,13 +117,13 @@ class ThermostatDevice(ClimateEntity): """Return the temperature we try to reach.""" return self._setpoint - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_AUTO: + if hvac_mode == HVACMode.AUTO: self.thermostat.mode = 1 - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: self.thermostat.mode = 2 - elif hvac_mode == HVAC_MODE_OFF: + elif hvac_mode == HVACMode.OFF: self.thermostat.mode = 0 def set_temperature(self, **kwargs): diff --git a/homeassistant/components/oncue/manifest.json b/homeassistant/components/oncue/manifest.json index 9991d35ed18..e0533129d94 100644 --- a/homeassistant/components/oncue/manifest.json +++ b/homeassistant/components/oncue/manifest.json @@ -9,7 +9,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/oncue", - "requirements": ["aiooncue==0.3.2"], + "requirements": ["aiooncue==0.3.4"], "codeowners": ["@bdraco"], "iot_class": "cloud_polling", "loggers": ["aiooncue"] diff --git a/homeassistant/components/ondilo_ico/manifest.json b/homeassistant/components/ondilo_ico/manifest.json index d3d9c7d1376..0fd542eab1d 100644 --- a/homeassistant/components/ondilo_ico/manifest.json +++ b/homeassistant/components/ondilo_ico/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ondilo_ico", "requirements": ["ondilo==0.2.0"], - "dependencies": ["http"], + "dependencies": ["auth"], "codeowners": ["@JeromeHXP"], "iot_class": "cloud_polling", "loggers": ["ondilo"] diff --git a/homeassistant/components/ondilo_ico/translations/hu.json b/homeassistant/components/ondilo_ico/translations/hu.json index a6979721779..91e05b319b6 100644 --- a/homeassistant/components/ondilo_ico/translations/hu.json +++ b/homeassistant/components/ondilo_ico/translations/hu.json @@ -9,7 +9,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/ondilo_ico/translations/it.json b/homeassistant/components/ondilo_ico/translations/it.json index 42536508716..f1e5fe58fb1 100644 --- a/homeassistant/components/ondilo_ico/translations/it.json +++ b/homeassistant/components/ondilo_ico/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione." }, "create_entry": { "default": "Autenticazione riuscita" diff --git a/homeassistant/components/onewire/translations/es.json b/homeassistant/components/onewire/translations/es.json index a2d8da218ba..d789e46875f 100644 --- a/homeassistant/components/onewire/translations/es.json +++ b/homeassistant/components/onewire/translations/es.json @@ -22,5 +22,10 @@ "title": "Configurar 1 cable" } } + }, + "options": { + "error": { + "device_not_selected": "Seleccionar los dispositivos a configurar" + } } } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/fr.json b/homeassistant/components/onewire/translations/fr.json index 0c75fb23ad1..08e3d3cf18b 100644 --- a/homeassistant/components/onewire/translations/fr.json +++ b/homeassistant/components/onewire/translations/fr.json @@ -29,7 +29,8 @@ }, "step": { "ack_no_options": { - "description": "Il n'y a pas d'option pour l'impl\u00e9mentation de SysBus" + "description": "Il n'y a pas d'option pour l'impl\u00e9mentation de SysBus", + "title": "Options de bus syst\u00e8me OneWire" }, "configure_device": { "data": { diff --git a/homeassistant/components/onewire/translations/nl.json b/homeassistant/components/onewire/translations/nl.json index cc310900d92..3ea1fd8e13c 100644 --- a/homeassistant/components/onewire/translations/nl.json +++ b/homeassistant/components/onewire/translations/nl.json @@ -29,6 +29,10 @@ }, "step": { "ack_no_options": { + "data": { + "one": "Leeg", + "other": "Ander" + }, "description": "Er zijn geen opties voor de SysBus implementatie", "title": "OneWire SysBus opties" }, diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index aafcbf6a54d..381f48b0b16 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -7,18 +7,12 @@ import eiscp from eiscp import eISCP import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - DOMAIN, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, @@ -41,23 +35,19 @@ DEFAULT_NAME = "Onkyo Receiver" SUPPORTED_MAX_VOLUME = 100 DEFAULT_RECEIVER_MAX_VOLUME = 80 -SUPPORT_ONKYO = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_STEP - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA -) SUPPORT_ONKYO_WO_VOLUME = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA +) +SUPPORT_ONKYO = ( + SUPPORT_ONKYO_WO_VOLUME + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_STEP ) KNOWN_HOSTS: list[str] = [] @@ -254,6 +244,8 @@ def setup_platform( class OnkyoDevice(MediaPlayerEntity): """Representation of an Onkyo device.""" + _attr_supported_features = SUPPORT_ONKYO + def __init__( self, receiver, @@ -389,11 +381,6 @@ class OnkyoDevice(MediaPlayerEntity): """Return boolean indicating mute status.""" return self._muted - @property - def supported_features(self): - """Return media player features that are supported.""" - return SUPPORT_ONKYO - @property def source(self): """Return the current input source of the device.""" diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 044a37edd72..8956f0ae2f9 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -2,13 +2,8 @@ from onvif.exceptions import ONVIFAuthError, ONVIFError, ONVIFTimeoutError from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, @@ -16,51 +11,17 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import config_per_platform -from homeassistant.helpers.typing import ConfigType from .const import ( CONF_RTSP_TRANSPORT, CONF_SNAPSHOT_AUTH, DEFAULT_ARGUMENTS, - DEFAULT_NAME, - DEFAULT_PASSWORD, - DEFAULT_PORT, - DEFAULT_USERNAME, DOMAIN, RTSP_TRANS_PROTOCOLS, ) from .device import ONVIFDevice -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the ONVIF component.""" - # Import from yaml - configs = {} - for p_type, p_config in config_per_platform(config, "camera"): - if p_type != DOMAIN: - continue - - config = p_config.copy() - if config[CONF_HOST] not in configs: - configs[config[CONF_HOST]] = { - CONF_HOST: config[CONF_HOST], - CONF_NAME: config.get(CONF_NAME, DEFAULT_NAME), - CONF_PASSWORD: config.get(CONF_PASSWORD, DEFAULT_PASSWORD), - CONF_PORT: config.get(CONF_PORT, DEFAULT_PORT), - CONF_USERNAME: config.get(CONF_USERNAME, DEFAULT_USERNAME), - } - - for conf in configs.values(): - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=conf - ) - ) - - return True - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up ONVIF from a config entry.""" if DOMAIN not in hass.data: diff --git a/homeassistant/components/onvif/binary_sensor.py b/homeassistant/components/onvif/binary_sensor.py index 2cc0f2c5d52..2e72d331d3d 100644 --- a/homeassistant/components/onvif/binary_sensor.py +++ b/homeassistant/components/onvif/binary_sensor.py @@ -3,11 +3,15 @@ from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.restore_state import RestoreEntity from .base import ONVIFBaseEntity from .const import DOMAIN +from .device import ONVIFDevice async def async_setup_entry( @@ -23,6 +27,13 @@ async def async_setup_entry( for event in device.events.get_platform("binary_sensor") } + ent_reg = er.async_get(hass) + for entry in er.async_entries_for_config_entry(ent_reg, config_entry.entry_id): + if entry.domain == "binary_sensor" and entry.unique_id not in entities: + entities[entry.unique_id] = ONVIFBinarySensor( + entry.unique_id, device, entry + ) + async_add_entities(entities.values()) @callback @@ -40,51 +51,39 @@ async def async_setup_entry( return True -class ONVIFBinarySensor(ONVIFBaseEntity, BinarySensorEntity): +class ONVIFBinarySensor(ONVIFBaseEntity, RestoreEntity, BinarySensorEntity): """Representation of a binary ONVIF event.""" - def __init__(self, uid, device): + _attr_should_poll = False + + def __init__(self, uid, device: ONVIFDevice, entry: er.RegistryEntry | None = None): """Initialize the ONVIF binary sensor.""" - ONVIFBaseEntity.__init__(self, device) - BinarySensorEntity.__init__(self) + self._attr_unique_id = uid + if entry is not None: + self._attr_device_class = entry.original_device_class + self._attr_entity_category = entry.entity_category + self._attr_name = entry.name + else: + event = device.events.get_uid(uid) + self._attr_device_class = event.device_class + self._attr_entity_category = event.entity_category + self._attr_entity_registry_enabled_default = event.entity_enabled + self._attr_name = f"{device.name} {event.name}" + self._attr_is_on = event.value - self.uid = uid + super().__init__(device) @property - def is_on(self) -> bool: - """Return true if event is active.""" - return self.device.events.get_uid(self.uid).value - - @property - def name(self) -> str: - """Return the name of the event.""" - return self.device.events.get_uid(self.uid).name - - @property - def device_class(self) -> str | None: - """Return the class of this device, from component DEVICE_CLASSES.""" - return self.device.events.get_uid(self.uid).device_class - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self.uid - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return self.device.events.get_uid(self.uid).entity_enabled - - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state. - - False if entity pushes its state to HA. - """ - return False + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + if (event := self.device.events.get_uid(self._attr_unique_id)) is not None: + return event.value + return self._attr_is_on async def async_added_to_hass(self): """Connect to dispatcher listening for entity data notifications.""" self.async_on_remove( self.device.events.async_add_listener(self.async_write_ha_state) ) + if (last_state := await self.async_get_last_state()) is not None: + self._attr_is_on = last_state.state == STATE_ON diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index c5f68661e69..3475df241b0 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -7,7 +7,7 @@ import voluptuous as vol from yarl import URL from homeassistant.components import ffmpeg -from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, get_ffmpeg_manager from homeassistant.config_entries import ConfigEntry from homeassistant.const import HTTP_BASIC_AUTHENTICATION @@ -88,6 +88,8 @@ async def async_setup_entry( class ONVIFCameraEntity(ONVIFBaseEntity, Camera): """Representation of an ONVIF camera.""" + _attr_supported_features = CameraEntityFeature.STREAM + def __init__(self, device, profile): """Initialize ONVIF camera entity.""" ONVIFBaseEntity.__init__(self, device, profile) @@ -101,15 +103,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): ) self._stream_uri = None - @property - def supported_features(self) -> int: - """Return supported features.""" - return SUPPORT_STREAM - @property def name(self) -> str: """Return the name of this camera.""" - return f"{self.device.name} - {self.profile.name}" + return f"{self.device.name} {self.profile.name}" @property def unique_id(self) -> str: diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index b4193f0def7..c4579702675 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -261,10 +261,6 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): finally: await device.close() - async def async_step_import(self, user_input): - """Handle import.""" - return await self.async_step_configure(user_input) - class OnvifOptionsFlowHandler(config_entries.OptionsFlow): """Handle ONVIF options.""" diff --git a/homeassistant/components/onvif/const.py b/homeassistant/components/onvif/const.py index dc0688c4b30..3a2e802a5a0 100644 --- a/homeassistant/components/onvif/const.py +++ b/homeassistant/components/onvif/const.py @@ -5,10 +5,7 @@ LOGGER = logging.getLogger(__package__) DOMAIN = "onvif" -DEFAULT_NAME = "ONVIF Camera" -DEFAULT_PORT = 5000 -DEFAULT_USERNAME = "admin" -DEFAULT_PASSWORD = "888888" +DEFAULT_PORT = 80 DEFAULT_ARGUMENTS = "-pred 1" CONF_DEVICE_ID = "deviceid" diff --git a/homeassistant/components/onvif/diagnostics.py b/homeassistant/components/onvif/diagnostics.py new file mode 100644 index 00000000000..eb818f53a3a --- /dev/null +++ b/homeassistant/components/onvif/diagnostics.py @@ -0,0 +1,32 @@ +"""Diagnostics support for ONVIF.""" +from __future__ import annotations + +from dataclasses import asdict +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .device import ONVIFDevice + +REDACT_CONFIG = {CONF_HOST, CONF_PASSWORD, CONF_USERNAME} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + device: ONVIFDevice = hass.data[DOMAIN][entry.unique_id] + data: dict[str, Any] = {} + + data["config"] = async_redact_data(entry.as_dict(), REDACT_CONFIG) + data["device"] = { + "info": asdict(device.info), + "capabilities": asdict(device.capabilities), + "profiles": [asdict(profile) for profile in device.profiles], + } + + return data diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index fcb8e39cf54..3b4ae981677 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -221,14 +221,14 @@ class EventManager: event = await parser(self.unique_id, msg) if not event: - LOGGER.warning("Unable to parse event from %s: %s", self.unique_id, msg) + LOGGER.info("Unable to parse event from %s: %s", self.unique_id, msg) return self._events[event.uid] = event - def get_uid(self, uid) -> Event: + def get_uid(self, uid) -> Event | None: """Retrieve event for given id.""" - return self._events[uid] + return self._events.get(uid) def get_platform(self, platform) -> list[Event]: """Retrieve events for given platform.""" diff --git a/homeassistant/components/onvif/models.py b/homeassistant/components/onvif/models.py index feda891f772..dea613e3c1c 100644 --- a/homeassistant/components/onvif/models.py +++ b/homeassistant/components/onvif/models.py @@ -4,6 +4,8 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from homeassistant.helpers.entity import EntityCategory + @dataclass class DeviceInfo: @@ -72,4 +74,5 @@ class Event: device_class: str = None unit_of_measurement: str = None value: Any = None + entity_category: EntityCategory | None = None entity_enabled: bool = True diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index b518dbbb451..5141f25cbef 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -1,7 +1,9 @@ """ONVIF event parsers.""" from collections.abc import Callable, Coroutine +import datetime from typing import Any +from homeassistant.helpers.entity import EntityCategory from homeassistant.util import dt as dt_util from homeassistant.util.decorator import Registry @@ -10,6 +12,18 @@ from .models import Event PARSERS: Registry[str, Callable[[str, Any], Coroutine[Any, Any, Event]]] = Registry() +def datetime_or_zero(value: str) -> datetime: + """Convert strings to datetimes, if invalid, return datetime.min.""" + # To handle cameras that return times like '0000-00-00T00:00:00Z' (e.g. hikvision) + try: + ret = dt_util.parse_datetime(value) + except ValueError: + return datetime.datetime.min + if ret is None: + return datetime.datetime.min + return ret + + @PARSERS.register("tns1:VideoSource/MotionAlarm") # pylint: disable=protected-access async def async_parse_motion_alarm(uid: str, msg) -> Event: @@ -21,7 +35,7 @@ async def async_parse_motion_alarm(uid: str, msg) -> Event: source = msg.Message._value_1.Source.SimpleItem[0].Value return Event( f"{uid}_{msg.Topic._value_1}_{source}", - f"{source} Motion Alarm", + "Motion Alarm", "binary_sensor", "motion", None, @@ -44,11 +58,12 @@ async def async_parse_image_too_blurry(uid: str, msg) -> Event: source = msg.Message._value_1.Source.SimpleItem[0].Value return Event( f"{uid}_{msg.Topic._value_1}_{source}", - f"{source} Image Too Blurry", + "Image Too Blurry", "binary_sensor", "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -67,11 +82,12 @@ async def async_parse_image_too_dark(uid: str, msg) -> Event: source = msg.Message._value_1.Source.SimpleItem[0].Value return Event( f"{uid}_{msg.Topic._value_1}_{source}", - f"{source} Image Too Dark", + "Image Too Dark", "binary_sensor", "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -90,11 +106,12 @@ async def async_parse_image_too_bright(uid: str, msg) -> Event: source = msg.Message._value_1.Source.SimpleItem[0].Value return Event( f"{uid}_{msg.Topic._value_1}_{source}", - f"{source} Image Too Bright", + "Image Too Bright", "binary_sensor", "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -113,7 +130,7 @@ async def async_parse_scene_change(uid: str, msg) -> Event: source = msg.Message._value_1.Source.SimpleItem[0].Value return Event( f"{uid}_{msg.Topic._value_1}_{source}", - f"{source} Global Scene Change", + "Global Scene Change", "binary_sensor", "problem", None, @@ -144,7 +161,7 @@ async def async_parse_detected_sound(uid: str, msg) -> Event: return Event( f"{uid}_{msg.Topic._value_1}_{audio_source}_{audio_analytics}_{rule}", - f"{rule} Detected Sound", + "Detected Sound", "binary_sensor", "sound", None, @@ -175,7 +192,7 @@ async def async_parse_field_detector(uid: str, msg) -> Event: evt = Event( f"{uid}_{msg.Topic._value_1}_{video_source}_{video_analytics}_{rule}", - f"{rule} Field Detection", + "Field Detection", "binary_sensor", "motion", None, @@ -207,7 +224,7 @@ async def async_parse_cell_motion_detector(uid: str, msg) -> Event: return Event( f"{uid}_{msg.Topic._value_1}_{video_source}_{video_analytics}_{rule}", - f"{rule} Cell Motion Detection", + "Cell Motion Detection", "binary_sensor", "motion", None, @@ -238,11 +255,11 @@ async def async_parse_motion_region_detector(uid: str, msg) -> Event: return Event( f"{uid}_{msg.Topic._value_1}_{video_source}_{video_analytics}_{rule}", - f"{rule} Motion Region Detection", + "Motion Region Detection", "binary_sensor", "motion", None, - msg.Message._value_1.Data.SimpleItem[0].Value == "true", + msg.Message._value_1.Data.SimpleItem[0].Value in ["1", "true"], ) except (AttributeError, KeyError): return None @@ -269,11 +286,54 @@ async def async_parse_tamper_detector(uid: str, msg) -> Event: return Event( f"{uid}_{msg.Topic._value_1}_{video_source}_{video_analytics}_{rule}", - f"{rule} Tamper Detection", + "Tamper Detection", "binary_sensor", "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, + ) + except (AttributeError, KeyError): + return None + + +@PARSERS.register("tns1:Device/Trigger/DigitalInput") +# pylint: disable=protected-access +async def async_parse_digital_input(uid: str, msg) -> Event: + """Handle parsing event message. + + Topic: tns1:Device/Trigger/DigitalInput + """ + try: + source = msg.Message._value_1.Source.SimpleItem[0].Value + return Event( + f"{uid}_{msg.Topic._value_1}_{source}", + "Digital Input", + "binary_sensor", + None, + None, + msg.Message._value_1.Data.SimpleItem[0].Value == "true", + ) + except (AttributeError, KeyError): + return None + + +@PARSERS.register("tns1:Device/Trigger/Relay") +# pylint: disable=protected-access +async def async_parse_relay(uid: str, msg) -> Event: + """Handle parsing event message. + + Topic: tns1:Device/Trigger/Relay + """ + try: + source = msg.Message._value_1.Source.SimpleItem[0].Value + return Event( + f"{uid}_{msg.Topic._value_1}_{source}", + "Relay Triggered", + "binary_sensor", + None, + None, + msg.Message._value_1.Data.SimpleItem[0].Value == "active", ) except (AttributeError, KeyError): return None @@ -295,6 +355,7 @@ async def async_parse_storage_failure(uid: str, msg) -> Event: "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -319,6 +380,7 @@ async def async_parse_processor_usage(uid: str, msg) -> Event: None, "percent", int(usage), + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -332,17 +394,17 @@ async def async_parse_last_reboot(uid: str, msg) -> Event: Topic: tns1:Monitoring/OperatingTime/LastReboot """ try: + date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) return Event( f"{uid}_{msg.Topic._value_1}", "Last Reboot", "sensor", "timestamp", None, - dt_util.as_local( - dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) - ), + dt_util.as_local(date_time), + EntityCategory.DIAGNOSTIC, ) - except (AttributeError, KeyError, ValueError): + except (AttributeError, KeyError): return None @@ -354,18 +416,42 @@ async def async_parse_last_reset(uid: str, msg) -> Event: Topic: tns1:Monitoring/OperatingTime/LastReset """ try: + date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) return Event( f"{uid}_{msg.Topic._value_1}", "Last Reset", "sensor", "timestamp", None, - dt_util.as_local( - dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) - ), + dt_util.as_local(date_time), + EntityCategory.DIAGNOSTIC, entity_enabled=False, ) - except (AttributeError, KeyError, ValueError): + except (AttributeError, KeyError): + return None + + +@PARSERS.register("tns1:Monitoring/Backup/Last") +# pylint: disable=protected-access +async def async_parse_backup_last(uid: str, msg) -> Event: + """Handle parsing event message. + + Topic: tns1:Monitoring/Backup/Last + """ + + try: + date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) + return Event( + f"{uid}_{msg.Topic._value_1}", + "Last Backup", + "sensor", + "timestamp", + None, + dt_util.as_local(date_time), + EntityCategory.DIAGNOSTIC, + entity_enabled=False, + ) + except (AttributeError, KeyError): return None @@ -377,18 +463,18 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: Topic: tns1:Monitoring/OperatingTime/LastClockSynchronization """ try: + date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) return Event( f"{uid}_{msg.Topic._value_1}", "Last Clock Synchronization", "sensor", "timestamp", None, - dt_util.as_local( - dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) - ), + dt_util.as_local(date_time), + EntityCategory.DIAGNOSTIC, entity_enabled=False, ) - except (AttributeError, KeyError, ValueError): + except (AttributeError, KeyError): return None @@ -397,18 +483,19 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: async def async_parse_jobstate(uid: str, msg) -> Event: """Handle parsing event message. - Topic: tns1:RecordingConfig/JobState* + Topic: tns1:RecordingConfig/JobState """ try: source = msg.Message._value_1.Source.SimpleItem[0].Value return Event( f"{uid}_{msg.Topic._value_1}_{source}", - f"{source} JobState", + "Recording Job State", "binary_sensor", None, None, msg.Message._value_1.Data.SimpleItem[0].Value == "Active", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index 6b219a8887d..67c0ee3da3f 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -1,13 +1,18 @@ """Support for ONVIF binary sensors.""" from __future__ import annotations -from homeassistant.components.sensor import SensorEntity +from datetime import date, datetime + +from homeassistant.components.sensor import RestoreSensor from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType from .base import ONVIFBaseEntity from .const import DOMAIN +from .device import ONVIFDevice async def async_setup_entry( @@ -23,6 +28,11 @@ async def async_setup_entry( for event in device.events.get_platform("sensor") } + ent_reg = er.async_get(hass) + for entry in er.async_entries_for_config_entry(ent_reg, config_entry.entry_id): + if entry.domain == "sensor" and entry.unique_id not in entities: + entities[entry.unique_id] = ONVIFSensor(entry.unique_id, device, entry) + async_add_entities(entities.values()) @callback @@ -40,55 +50,41 @@ async def async_setup_entry( return True -class ONVIFSensor(ONVIFBaseEntity, SensorEntity): +class ONVIFSensor(ONVIFBaseEntity, RestoreSensor): """Representation of a ONVIF sensor event.""" - def __init__(self, uid, device): + _attr_should_poll = False + + def __init__(self, uid, device: ONVIFDevice, entry: er.RegistryEntry | None = None): """Initialize the ONVIF binary sensor.""" - self.uid = uid + self._attr_unique_id = uid + if entry is not None: + self._attr_device_class = entry.original_device_class + self._attr_entity_category = entry.entity_category + self._attr_name = entry.name + self._attr_native_unit_of_measurement = entry.unit_of_measurement + else: + event = device.events.get_uid(uid) + self._attr_device_class = event.device_class + self._attr_entity_category = event.entity_category + self._attr_entity_registry_enabled_default = event.entity_enabled + self._attr_name = f"{device.name} {event.name}" + self._attr_native_unit_of_measurement = event.unit_of_measurement + self._attr_native_value = event.value super().__init__(device) @property - def native_value(self) -> None | str | int | float: - """Return the state of the entity.""" - return self.device.events.get_uid(self.uid).value - - @property - def name(self): - """Return the name of the event.""" - return self.device.events.get_uid(self.uid).name - - @property - def device_class(self) -> str | None: - """Return the class of this device, from component DEVICE_CLASSES.""" - return self.device.events.get_uid(self.uid).device_class - - @property - def native_unit_of_measurement(self) -> str | None: - """Return the unit of measurement of this entity, if any.""" - return self.device.events.get_uid(self.uid).unit_of_measurement - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self.uid - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return self.device.events.get_uid(self.uid).entity_enabled - - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state. - - False if entity pushes its state to HA. - """ - return False + def native_value(self) -> StateType | date | datetime: + """Return the value reported by the sensor.""" + if (event := self.device.events.get_uid(self._attr_unique_id)) is not None: + return event.value + return self._attr_native_value async def async_added_to_hass(self): """Connect to dispatcher listening for entity data notifications.""" self.async_on_remove( self.device.events.async_add_listener(self.async_write_ha_state) ) + if (last_sensor_data := await self.async_get_last_sensor_data()) is not None: + self._attr_native_value = last_sensor_data.native_value diff --git a/homeassistant/components/onvif/translations/hu.json b/homeassistant/components/onvif/translations/hu.json index 6b777b76ab0..3754243a740 100644 --- a/homeassistant/components/onvif/translations/hu.json +++ b/homeassistant/components/onvif/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "no_h264": "Nem voltak el\u00e9rhet\u0151 H264 streamek. Ellen\u0151rizze a profil konfigur\u00e1ci\u00f3j\u00e1t a k\u00e9sz\u00fcl\u00e9ken.", "no_mac": "Nem siker\u00fclt konfigur\u00e1lni az egyedi azonos\u00edt\u00f3t az ONVIF eszk\u00f6zh\u00f6z.", "onvif_error": "Hiba t\u00f6rt\u00e9nt az ONVIF eszk\u00f6z be\u00e1ll\u00edt\u00e1sakor. Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt ellen\u0151rizze a napl\u00f3kat." @@ -21,7 +21,7 @@ "configure": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" @@ -44,7 +44,7 @@ "manual_input": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "port": "Port" }, "title": "ONVIF eszk\u00f6z konfigur\u00e1l\u00e1sa" @@ -53,7 +53,7 @@ "data": { "auto": "Automatikus keres\u00e9s" }, - "description": "A K\u00fcld\u00e9s gombra kattintva olyan ONVIF-eszk\u00f6z\u00f6ket keres\u00fcnk a h\u00e1l\u00f3zat\u00e1ban, amelyek t\u00e1mogatj\u00e1k az S profilt.\n\nEgyes gy\u00e1rt\u00f3k alap\u00e9rtelmez\u00e9s szerint elkezdt\u00e9k letiltani az ONVIF-et. Ellen\u0151rizze, hogy az ONVIF enged\u00e9lyezve van-e a kamera konfigur\u00e1ci\u00f3j\u00e1ban.", + "description": "A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben olyan ONVIF-eszk\u00f6z\u00f6ket keres\u00fcnk a h\u00e1l\u00f3zat\u00e1ban, amelyek t\u00e1mogatj\u00e1k az S profilt.\n\nEgyes gy\u00e1rt\u00f3k alap\u00e9rtelmez\u00e9s szerint elkezdt\u00e9k letiltani az ONVIF-et. Ellen\u0151rizze, hogy az ONVIF enged\u00e9lyezve van-e a kamera konfigur\u00e1ci\u00f3j\u00e1ban.", "title": "ONVIF eszk\u00f6z be\u00e1ll\u00edt\u00e1sa" } } diff --git a/homeassistant/components/open_meteo/config_flow.py b/homeassistant/components/open_meteo/config_flow.py index 7f63b834bb9..7a603f887f0 100644 --- a/homeassistant/components/open_meteo/config_flow.py +++ b/homeassistant/components/open_meteo/config_flow.py @@ -9,7 +9,7 @@ from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ZONE from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.selector import selector +from homeassistant.helpers.selector import EntitySelector, EntitySelectorConfig from .const import DOMAIN @@ -37,8 +37,8 @@ class OpenMeteoFlowHandler(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=vol.Schema( { - vol.Required(CONF_ZONE): selector( - {"entity": {"domain": ZONE_DOMAIN}} + vol.Required(CONF_ZONE): EntitySelector( + EntitySelectorConfig(domain=ZONE_DOMAIN), ), } ), diff --git a/homeassistant/components/open_meteo/translations/ca.json b/homeassistant/components/open_meteo/translations/ca.json index 4fb1a502b6f..a401eeaa99e 100644 --- a/homeassistant/components/open_meteo/translations/ca.json +++ b/homeassistant/components/open_meteo/translations/ca.json @@ -5,7 +5,7 @@ "data": { "zone": "Zona" }, - "description": "Selecciona la ubicaci\u00f3 que s'utilitzar\u00e0 per a la predicci\u00f3 meteorol\u00f2gica" + "description": "Selecciona la ubicaci\u00f3 que s'utilitzar\u00e0 per a la previsi\u00f3 meteorol\u00f2gica" } } } diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index 86edd074ed2..9d5bd6e5cb6 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -2,10 +2,9 @@ import logging from homeassistant.components.cover import ( - SUPPORT_CLOSE, - SUPPORT_OPEN, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING @@ -33,7 +32,7 @@ class OpenGarageCover(OpenGarageEntity, CoverEntity): """Representation of a OpenGarage cover.""" _attr_device_class = CoverDeviceClass.GARAGE - _attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE def __init__(self, open_garage_data_coordinator, device_id): """Initialize the cover.""" diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index 21c3c465775..95d9ca57992 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -11,26 +11,14 @@ from openhomedevice.device import Device import voluptuous as vol from homeassistant.components import media_source -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform @@ -39,7 +27,11 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ATTR_PIN_INDEX, DATA_OPENHOME, SERVICE_INVOKE_PIN -SUPPORT_OPENHOME = SUPPORT_SELECT_SOURCE | SUPPORT_TURN_OFF | SUPPORT_TURN_ON +SUPPORT_OPENHOME = ( + MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON +) _LOGGER = logging.getLogger(__name__) @@ -113,7 +105,7 @@ class OpenhomeDevice(MediaPlayerEntity): self._transport_state = None self._volume_level = None self._volume_muted = None - self._supported_features = SUPPORT_OPENHOME + self._attr_supported_features = SUPPORT_OPENHOME self._source_names = [] self._source_index = {} self._source = {} @@ -134,13 +126,15 @@ class OpenhomeDevice(MediaPlayerEntity): self._track_information = await self._device.track_info() self._source = await self._device.source() self._name = await self._device.room() - self._supported_features = SUPPORT_OPENHOME + self._attr_supported_features = SUPPORT_OPENHOME source_index = {} source_names = [] if self._device.volume_enabled: - self._supported_features |= ( - SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET + self._attr_supported_features |= ( + MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET ) self._volume_level = await self._device.volume() / 100.0 self._volume_muted = await self._device.is_muted() @@ -153,20 +147,20 @@ class OpenhomeDevice(MediaPlayerEntity): self._source_names = source_names if self._source["type"] == "Radio": - self._supported_features |= ( - SUPPORT_STOP - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_BROWSE_MEDIA + self._attr_supported_features |= ( + MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA ) if self._source["type"] in ("Playlist", "Spotify"): - self._supported_features |= ( - SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_BROWSE_MEDIA + self._attr_supported_features |= ( + MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA ) if self._in_standby: @@ -262,11 +256,6 @@ class OpenhomeDevice(MediaPlayerEntity): """Return the name of the device.""" return self._name - @property - def supported_features(self): - """Flag of features commands that are supported.""" - return self._supported_features - @property def unique_id(self): """Return a unique ID.""" diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index e6b15688fb6..0350c446a3f 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -1,19 +1,17 @@ """Support for OpenTherm Gateway climate devices.""" +from __future__ import annotations + import logging from pyotgw import vars as gw_vars from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, PRESET_AWAY, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -43,8 +41,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_FLOOR_TEMP = False -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - async def async_setup_entry( hass: HomeAssistant, @@ -66,6 +62,10 @@ async def async_setup_entry( class OpenThermClimate(ClimateEntity): """Representation of a climate device.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, gw_dev, options): """Initialize the device.""" self._gateway = gw_dev @@ -78,9 +78,9 @@ class OpenThermClimate(ClimateEntity): self.temp_set_precision = options.get(CONF_SET_PRECISION) self.temporary_ovrd_mode = options.get(CONF_TEMPORARY_OVRD_MODE, True) self._available = False - self._current_operation = None + self._current_operation: HVACAction | None = None self._current_temperature = None - self._hvac_mode = HVAC_MODE_HEAT + self._hvac_mode = HVACMode.HEAT self._new_target_temperature = None self._target_temperature = None self._away_mode_a = None @@ -123,13 +123,13 @@ class OpenThermClimate(ClimateEntity): flame_on = status[gw_vars.BOILER].get(gw_vars.DATA_SLAVE_FLAME_ON) cooling_active = status[gw_vars.BOILER].get(gw_vars.DATA_SLAVE_COOLING_ACTIVE) if ch_active and flame_on: - self._current_operation = CURRENT_HVAC_HEAT - self._hvac_mode = HVAC_MODE_HEAT + self._current_operation = HVACAction.HEATING + self._hvac_mode = HVACMode.HEAT elif cooling_active: - self._current_operation = CURRENT_HVAC_COOL - self._hvac_mode = HVAC_MODE_COOL + self._current_operation = HVACAction.COOLING + self._hvac_mode = HVACMode.COOL else: - self._current_operation = CURRENT_HVAC_IDLE + self._current_operation = HVACAction.IDLE self._current_temperature = status[gw_vars.THERMOSTAT].get( gw_vars.DATA_ROOM_TEMP @@ -212,21 +212,21 @@ class OpenThermClimate(ClimateEntity): return TEMP_CELSIUS @property - def hvac_action(self): + def hvac_action(self) -> HVACAction | None: """Return current HVAC operation.""" return self._current_operation @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current HVAC mode.""" return self._hvac_mode @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return available HVAC modes.""" return [] - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the HVAC mode.""" _LOGGER.warning("Changing HVAC mode is not supported") @@ -284,11 +284,6 @@ class OpenThermClimate(ClimateEntity): ) self.async_write_ha_state() - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - @property def min_temp(self): """Return the minimum temperature.""" diff --git a/homeassistant/components/opentherm_gw/translations/hu.json b/homeassistant/components/opentherm_gw/translations/hu.json index 3127dc523ce..e2a284d7dd1 100644 --- a/homeassistant/components/opentherm_gw/translations/hu.json +++ b/homeassistant/components/opentherm_gw/translations/hu.json @@ -10,7 +10,7 @@ "data": { "device": "El\u00e9r\u00e9si \u00fat vagy URL", "id": "ID", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "title": "OpenTherm \u00e1tj\u00e1r\u00f3" } diff --git a/homeassistant/components/openuv/manifest.json b/homeassistant/components/openuv/manifest.json index 08299ca5ddb..cd9bbfa8edf 100644 --- a/homeassistant/components/openuv/manifest.json +++ b/homeassistant/components/openuv/manifest.json @@ -3,7 +3,7 @@ "name": "OpenUV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/openuv", - "requirements": ["pyopenuv==2021.11.0"], + "requirements": ["pyopenuv==2022.04.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "loggers": ["pyopenuv"] diff --git a/homeassistant/components/openweathermap/translations/ca.json b/homeassistant/components/openweathermap/translations/ca.json index b3b1ebbb85e..f304a8d4f9f 100644 --- a/homeassistant/components/openweathermap/translations/ca.json +++ b/homeassistant/components/openweathermap/translations/ca.json @@ -15,9 +15,9 @@ "latitude": "Latitud", "longitude": "Longitud", "mode": "Mode", - "name": "Nom de la integraci\u00f3" + "name": "Nom" }, - "description": "Configura la integraci\u00f3 OpenWeatherMap. Per generar la clau API, ves a https://openweathermap.org/appid", + "description": "Per generar la clau API, v\u00e9s a https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/de.json b/homeassistant/components/openweathermap/translations/de.json index 615a642a859..f74065ca1a1 100644 --- a/homeassistant/components/openweathermap/translations/de.json +++ b/homeassistant/components/openweathermap/translations/de.json @@ -15,9 +15,9 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", "mode": "Modus", - "name": "Name der Integration" + "name": "Name" }, - "description": "Richte die OpenWeatherMap-Integration ein. Zum Generieren des API-Schl\u00fcssels gehe auf https://openweathermap.org/appid", + "description": "Um den API-Schl\u00fcssel zu generieren, besuche https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/en.json b/homeassistant/components/openweathermap/translations/en.json index 57feaccf6b4..55f8d0a2338 100644 --- a/homeassistant/components/openweathermap/translations/en.json +++ b/homeassistant/components/openweathermap/translations/en.json @@ -15,9 +15,9 @@ "latitude": "Latitude", "longitude": "Longitude", "mode": "Mode", - "name": "Name of the integration" + "name": "Name" }, - "description": "Set up OpenWeatherMap integration. To generate API key go to https://openweathermap.org/appid", + "description": "To generate API key go to https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/et.json b/homeassistant/components/openweathermap/translations/et.json index e548c07236e..0d991c99e3f 100644 --- a/homeassistant/components/openweathermap/translations/et.json +++ b/homeassistant/components/openweathermap/translations/et.json @@ -15,9 +15,9 @@ "latitude": "Laiuskraad", "longitude": "Pikkuskraad", "mode": "Re\u017eiim", - "name": "Sidumise nimi" + "name": "Nimi" }, - "description": "Seadista OpenWeatherMapi sidumine. API-v\u00f5tme loomiseks mine aadressile https://openweathermap.org/appid", + "description": "API-v\u00f5tme loomiseks mine aadressile https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/fr.json b/homeassistant/components/openweathermap/translations/fr.json index efa76d4d7e4..387c3eefb28 100644 --- a/homeassistant/components/openweathermap/translations/fr.json +++ b/homeassistant/components/openweathermap/translations/fr.json @@ -15,9 +15,9 @@ "latitude": "Latitude", "longitude": "Longitude", "mode": "Mode", - "name": "Nom de l'int\u00e9gration" + "name": "Nom" }, - "description": "Configurez l'int\u00e9gration OpenWeatherMap. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://openweathermap.org/appid", + "description": "Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/he.json b/homeassistant/components/openweathermap/translations/he.json index ca5e388ea98..cadadeb1865 100644 --- a/homeassistant/components/openweathermap/translations/he.json +++ b/homeassistant/components/openweathermap/translations/he.json @@ -15,7 +15,7 @@ "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", "mode": "\u05de\u05e6\u05d1", - "name": "\u05e9\u05dd \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1" + "name": "\u05e9\u05dd" }, "title": "\u05de\u05e4\u05ea OpenWeather" } diff --git a/homeassistant/components/openweathermap/translations/hu.json b/homeassistant/components/openweathermap/translations/hu.json index 99932ff5c68..f5a7dade833 100644 --- a/homeassistant/components/openweathermap/translations/hu.json +++ b/homeassistant/components/openweathermap/translations/hu.json @@ -15,9 +15,9 @@ "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", "mode": "M\u00f3d", - "name": "Az integr\u00e1ci\u00f3 neve" + "name": "Elnevez\u00e9s" }, - "description": "Az OpenWeatherMap integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sa. Az API kulcs l\u00e9trehoz\u00e1s\u00e1hoz l\u00e1togasson el a https://openweathermap.org/appid oldalra", + "description": "Az API-kulcs l\u00e9trehoz\u00e1s\u00e1hoz keresse fel a https://openweathermap.org/appid webhelyet", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/id.json b/homeassistant/components/openweathermap/translations/id.json index 61d2713f42a..d8ea0344cea 100644 --- a/homeassistant/components/openweathermap/translations/id.json +++ b/homeassistant/components/openweathermap/translations/id.json @@ -15,9 +15,9 @@ "latitude": "Lintang", "longitude": "Bujur", "mode": "Mode", - "name": "Nama integrasi" + "name": "Nama" }, - "description": "Siapkan integrasi OpenWeatherMap. Untuk membuat kunci API, buka https://openweathermap.org/appid", + "description": "Untuk membuat kunci API, buka https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/it.json b/homeassistant/components/openweathermap/translations/it.json index ffe49a466dc..133fa704109 100644 --- a/homeassistant/components/openweathermap/translations/it.json +++ b/homeassistant/components/openweathermap/translations/it.json @@ -15,9 +15,9 @@ "latitude": "Latitudine", "longitude": "Logitudine", "mode": "Modalit\u00e0", - "name": "Nome dell'integrazione" + "name": "Nome" }, - "description": "Configura l'integrazione di OpenWeatherMap. Per generare la chiave API, vai su https://openweathermap.org/appid", + "description": "Per generare la chiave API, vai su https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/nl.json b/homeassistant/components/openweathermap/translations/nl.json index 04f3c503353..1c72f10c6d9 100644 --- a/homeassistant/components/openweathermap/translations/nl.json +++ b/homeassistant/components/openweathermap/translations/nl.json @@ -15,9 +15,9 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad", "mode": "Mode", - "name": "Naam van de integratie" + "name": "Naam" }, - "description": "Stel OpenWeatherMap-integratie in. Ga naar https://openweathermap.org/appid om een API-sleutel te genereren", + "description": "Om een API sleutel te genereren ga naar https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/no.json b/homeassistant/components/openweathermap/translations/no.json index 7982628fe62..d751553465a 100644 --- a/homeassistant/components/openweathermap/translations/no.json +++ b/homeassistant/components/openweathermap/translations/no.json @@ -15,9 +15,9 @@ "latitude": "Breddegrad", "longitude": "Lengdegrad", "mode": "Modus", - "name": "Navn p\u00e5 integrasjon" + "name": "Navn" }, - "description": "Sett opp OpenWeatherMap-integrasjon. For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://openweathermap.org/appid", + "description": "For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/pl.json b/homeassistant/components/openweathermap/translations/pl.json index b3f31831f09..de8d857f168 100644 --- a/homeassistant/components/openweathermap/translations/pl.json +++ b/homeassistant/components/openweathermap/translations/pl.json @@ -15,9 +15,9 @@ "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "mode": "Tryb", - "name": "Nazwa integracji" + "name": "Nazwa" }, - "description": "Konfiguracja integracji OpenWeatherMap. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://openweathermap.org/appid", + "description": "Aby wygenerowa\u0107 klucz API, przejd\u017a do https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/pt-BR.json b/homeassistant/components/openweathermap/translations/pt-BR.json index dd88767bb61..795db96f36f 100644 --- a/homeassistant/components/openweathermap/translations/pt-BR.json +++ b/homeassistant/components/openweathermap/translations/pt-BR.json @@ -15,9 +15,9 @@ "latitude": "Latitude", "longitude": "Longitude", "mode": "Modo", - "name": "Nome da integra\u00e7\u00e3o" + "name": "Nome" }, - "description": "Configure a integra\u00e7\u00e3o do OpenWeatherMap. Para gerar a chave de API, acesse https://openweathermap.org/appid", + "description": "Para gerar a chave de API, acesse https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/ru.json b/homeassistant/components/openweathermap/translations/ru.json index 1a22b38d546..a0724e90f01 100644 --- a/homeassistant/components/openweathermap/translations/ru.json +++ b/homeassistant/components/openweathermap/translations/ru.json @@ -17,7 +17,7 @@ "mode": "\u0420\u0435\u0436\u0438\u043c", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 OpenWeatherMap. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://openweathermap.org/appid.", + "description": "\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://openweathermap.org/appid.", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/tr.json b/homeassistant/components/openweathermap/translations/tr.json index 83109869e39..6458852712e 100644 --- a/homeassistant/components/openweathermap/translations/tr.json +++ b/homeassistant/components/openweathermap/translations/tr.json @@ -15,9 +15,9 @@ "latitude": "Enlem", "longitude": "Boylam", "mode": "Mod", - "name": "Cihaz\u0131n ad\u0131" + "name": "Ad" }, - "description": "OpenWeatherMap entegrasyonunu ayarlay\u0131n. API anahtar\u0131 olu\u015fturmak i\u00e7in https://openweathermap.org/appid adresine gidin.", + "description": "API anahtar\u0131 olu\u015fturmak i\u00e7in https://openweathermap.org/appid adresine gidin.", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/zh-Hant.json b/homeassistant/components/openweathermap/translations/zh-Hant.json index 653fb373af3..fcebc12fe31 100644 --- a/homeassistant/components/openweathermap/translations/zh-Hant.json +++ b/homeassistant/components/openweathermap/translations/zh-Hant.json @@ -15,9 +15,9 @@ "latitude": "\u7def\u5ea6", "longitude": "\u7d93\u5ea6", "mode": "\u6a21\u5f0f", - "name": "\u6574\u5408\u540d\u7a31" + "name": "\u540d\u7a31" }, - "description": "\u6b32\u8a2d\u5b9a OpenWeatherMap \u6574\u5408\u3002\u8acb\u81f3 https://openweathermap.org/appid \u7522\u751f API \u91d1\u9470", + "description": "\u8acb\u81f3 https://openweathermap.org/appid \u4ee5\u7522\u751f API \u91d1\u9470", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/opple/light.py b/homeassistant/components/opple/light.py index 6965deb0791..a9e24029a13 100644 --- a/homeassistant/components/opple/light.py +++ b/homeassistant/components/opple/light.py @@ -10,8 +10,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR_TEMP, + ColorMode, LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME @@ -55,6 +54,9 @@ def setup_platform( class OppleLight(LightEntity): """Opple light device.""" + _attr_color_mode = ColorMode.COLOR_TEMP + _attr_supported_color_modes = {ColorMode.COLOR_TEMP} + def __init__(self, name, host): """Initialize an Opple light.""" @@ -105,11 +107,6 @@ class OppleLight(LightEntity): """Return maximum supported color temperature.""" return 333 - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP - def turn_on(self, **kwargs): """Instruct the light to turn on.""" _LOGGER.debug("Turn on light %s %s", self._device.ip, kwargs) diff --git a/homeassistant/components/overkiz/__init__.py b/homeassistant/components/overkiz/__init__.py index 66234eb4460..1132e269d04 100644 --- a/homeassistant/components/overkiz/__init__.py +++ b/homeassistant/components/overkiz/__init__.py @@ -123,7 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: identifiers={(DOMAIN, gateway.id)}, model=gateway.sub_type.beautify_name if gateway.sub_type else None, manufacturer=server.manufacturer, - name=gateway.type.beautify_name, + name=gateway.type.beautify_name if gateway.type else gateway.id, sw_version=gateway.connectivity.protocol_version, configuration_url=server.configuration_url, ) diff --git a/homeassistant/components/overkiz/alarm_control_panel.py b/homeassistant/components/overkiz/alarm_control_panel.py index 059f2562544..2229c583297 100644 --- a/homeassistant/components/overkiz/alarm_control_panel.py +++ b/homeassistant/components/overkiz/alarm_control_panel.py @@ -12,12 +12,7 @@ from pyoverkiz.types import StateType as OverkizStateType from homeassistant.components.alarm_control_panel import ( AlarmControlPanelEntity, AlarmControlPanelEntityDescription, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_TRIGGER, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -162,10 +157,10 @@ ALARM_DESCRIPTIONS: list[OverkizAlarmDescription] = [ key=UIWidget.TSKALARM_CONTROLLER, entity_registry_enabled_default=False, supported_features=( - SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_TRIGGER + AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.TRIGGER ), fn_state=_state_tsk_alarm_controller, alarm_disarm=OverkizCommand.ALARM_OFF, @@ -181,7 +176,9 @@ ALARM_DESCRIPTIONS: list[OverkizAlarmDescription] = [ OverkizAlarmDescription( key=UIWidget.STATEFUL_ALARM_CONTROLLER, supported_features=( - SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT + AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_NIGHT ), fn_state=_state_stateful_alarm_controller, alarm_disarm=OverkizCommand.ALARM_OFF, @@ -195,7 +192,8 @@ ALARM_DESCRIPTIONS: list[OverkizAlarmDescription] = [ # MyFoxAlarmController OverkizAlarmDescription( key=UIWidget.MY_FOX_ALARM_CONTROLLER, - supported_features=SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT, + supported_features=AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT, fn_state=_state_myfox_alarm_controller, alarm_disarm=OverkizCommand.DISARM, alarm_arm_night=OverkizCommand.PARTIAL, @@ -205,7 +203,9 @@ ALARM_DESCRIPTIONS: list[OverkizAlarmDescription] = [ OverkizAlarmDescription( key=UIWidget.ALARM_PANEL_CONTROLLER, supported_features=( - SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT + AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_NIGHT ), fn_state=_state_alarm_panel_controller, alarm_disarm=OverkizCommand.DISARM, diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py index 8756b768e52..0a397e9f2ad 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py @@ -5,26 +5,23 @@ from typing import cast from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState -from homeassistant.components.climate import ( - HVAC_MODE_OFF, - SUPPORT_PRESET_MODE, - ClimateEntity, -) +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, PRESET_COMFORT, PRESET_ECO, PRESET_NONE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.components.overkiz.entity import OverkizEntity from homeassistant.const import TEMP_CELSIUS PRESET_FROST_PROTECTION = "frost_protection" -OVERKIZ_TO_HVAC_MODES: dict[str, str] = { - OverkizCommandParam.ON: HVAC_MODE_HEAT, - OverkizCommandParam.COMFORT: HVAC_MODE_HEAT, - OverkizCommandParam.OFF: HVAC_MODE_OFF, +OVERKIZ_TO_HVAC_MODES: dict[str, HVACMode] = { + OverkizCommandParam.ON: HVACMode.HEAT, + OverkizCommandParam.COMFORT: HVACMode.HEAT, + OverkizCommandParam.OFF: HVACMode.OFF, } HVAC_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_HVAC_MODES.items()} @@ -43,17 +40,17 @@ class AtlanticElectricalHeater(OverkizEntity, ClimateEntity): _attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ] _attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] - _attr_supported_features = SUPPORT_PRESET_MODE + _attr_supported_features = ClimateEntityFeature.PRESET_MODE _attr_temperature_unit = TEMP_CELSIUS @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" return OVERKIZ_TO_HVAC_MODES[ cast(str, self.executor.select_state(OverkizState.CORE_ON_OFF)) ] - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" await self.executor.async_execute_command( OverkizCommand.SET_HEATING_LEVEL, HVAC_MODES_TO_OVERKIZ[hvac_mode] diff --git a/homeassistant/components/overkiz/coordinator.py b/homeassistant/components/overkiz/coordinator.py index d90a52ae409..b27051b1492 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -62,7 +62,7 @@ class OverkizDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Device]]): device.protocol in (Protocol.RTS, Protocol.INTERNAL) for device in devices ) self.executions: dict[str, dict[str, str]] = {} - self.areas = self._places_to_area(places) + self.areas = self._places_to_area(places) if places else None self.config_entry_id = config_entry_id async def _async_update_data(self) -> dict[str, Device]: diff --git a/homeassistant/components/overkiz/cover_entities/awning.py b/homeassistant/components/overkiz/cover_entities/awning.py index 19422d9c193..07665c2a5c8 100644 --- a/homeassistant/components/overkiz/cover_entities/awning.py +++ b/homeassistant/components/overkiz/cover_entities/awning.py @@ -7,11 +7,8 @@ from pyoverkiz.enums import OverkizCommand, OverkizState from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, + CoverEntityFeature, ) from .generic_cover import ( @@ -33,16 +30,16 @@ class Awning(OverkizGenericCover): supported_features: int = super().supported_features if self.executor.has_command(OverkizCommand.SET_DEPLOYMENT): - supported_features |= SUPPORT_SET_POSITION + supported_features |= CoverEntityFeature.SET_POSITION if self.executor.has_command(OverkizCommand.DEPLOY): - supported_features |= SUPPORT_OPEN + supported_features |= CoverEntityFeature.OPEN if self.executor.has_command(*COMMANDS_STOP): - supported_features |= SUPPORT_STOP + supported_features |= CoverEntityFeature.STOP if self.executor.has_command(OverkizCommand.UNDEPLOY): - supported_features |= SUPPORT_CLOSE + supported_features |= CoverEntityFeature.CLOSE return supported_features diff --git a/homeassistant/components/overkiz/cover_entities/generic_cover.py b/homeassistant/components/overkiz/cover_entities/generic_cover.py index 6e927d52ecf..e36d7a680b1 100644 --- a/homeassistant/components/overkiz/cover_entities/generic_cover.py +++ b/homeassistant/components/overkiz/cover_entities/generic_cover.py @@ -8,11 +8,8 @@ from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState from homeassistant.components.cover import ( ATTR_TILT_POSITION, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN_TILT, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP_TILT, CoverEntity, + CoverEntityFeature, ) from ..entity import OverkizEntity @@ -137,15 +134,15 @@ class OverkizGenericCover(OverkizEntity, CoverEntity): supported_features = 0 if self.executor.has_command(*COMMANDS_OPEN_TILT): - supported_features |= SUPPORT_OPEN_TILT + supported_features |= CoverEntityFeature.OPEN_TILT if self.executor.has_command(*COMMANDS_STOP_TILT): - supported_features |= SUPPORT_STOP_TILT + supported_features |= CoverEntityFeature.STOP_TILT if self.executor.has_command(*COMMANDS_CLOSE_TILT): - supported_features |= SUPPORT_CLOSE_TILT + supported_features |= CoverEntityFeature.CLOSE_TILT if self.executor.has_command(*COMMANDS_SET_TILT_POSITION): - supported_features |= SUPPORT_SET_TILT_POSITION + supported_features |= CoverEntityFeature.SET_TILT_POSITION return supported_features diff --git a/homeassistant/components/overkiz/cover_entities/vertical_cover.py b/homeassistant/components/overkiz/cover_entities/vertical_cover.py index 70bc8fb1654..f76f3849e83 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -13,11 +13,8 @@ from pyoverkiz.enums import ( from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, + CoverEntityFeature, ) from ..coordinator import OverkizDataUpdateCoordinator @@ -54,16 +51,16 @@ class VerticalCover(OverkizGenericCover): supported_features: int = super().supported_features if self.executor.has_command(OverkizCommand.SET_CLOSURE): - supported_features |= SUPPORT_SET_POSITION + supported_features |= CoverEntityFeature.SET_POSITION if self.executor.has_command(*COMMANDS_OPEN): - supported_features |= SUPPORT_OPEN + supported_features |= CoverEntityFeature.OPEN if self.executor.has_command(*COMMANDS_STOP): - supported_features |= SUPPORT_STOP + supported_features |= CoverEntityFeature.STOP if self.executor.has_command(*COMMANDS_CLOSE): - supported_features |= SUPPORT_CLOSE + supported_features |= CoverEntityFeature.CLOSE return supported_features diff --git a/homeassistant/components/overkiz/entity.py b/homeassistant/components/overkiz/entity.py index 7c42f415a65..a177766c292 100644 --- a/homeassistant/components/overkiz/entity.py +++ b/homeassistant/components/overkiz/entity.py @@ -66,6 +66,12 @@ class OverkizEntity(CoordinatorEntity[OverkizDataUpdateCoordinator]): or self.device.widget.value ) + suggested_area = ( + self.coordinator.areas[self.device.place_oid] + if self.coordinator.areas and self.device.place_oid + else None + ) + return DeviceInfo( identifiers={(DOMAIN, self.executor.base_device_url)}, name=self.device.label, @@ -76,7 +82,7 @@ class OverkizEntity(CoordinatorEntity[OverkizDataUpdateCoordinator]): self.executor.select_attribute(OverkizAttribute.CORE_FIRMWARE_REVISION), ), hw_version=self.device.controllable_name, - suggested_area=self.coordinator.areas[self.device.place_oid], + suggested_area=suggested_area, via_device=(DOMAIN, self.executor.get_gateway_id()), configuration_url=self.coordinator.client.server.configuration_url, ) diff --git a/homeassistant/components/overkiz/executor.py b/homeassistant/components/overkiz/executor.py index 2e9b0080815..127851db36a 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -1,7 +1,7 @@ """Class for helpers and communication with the OverKiz API.""" from __future__ import annotations -from typing import Any +from typing import Any, cast from urllib.parse import urlparse from pyoverkiz.enums import OverkizCommand, Protocol @@ -113,7 +113,9 @@ class OverkizExecutor: return True # Retrieve executions initiated outside Home Assistant via API - executions = await self.coordinator.client.get_current_executions() + executions = cast(Any, await self.coordinator.client.get_current_executions()) + # executions.action_group is typed incorrectly in the upstream library + # or the below code is incorrect. exec_id = next( ( execution.id diff --git a/homeassistant/components/overkiz/light.py b/homeassistant/components/overkiz/light.py index b640a184f58..0ad6cb0d9c5 100644 --- a/homeassistant/components/overkiz/light.py +++ b/homeassistant/components/overkiz/light.py @@ -8,9 +8,7 @@ from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_ONOFF, - COLOR_MODE_RGB, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -47,14 +45,14 @@ class OverkizLight(OverkizEntity, LightEntity): """Initialize a device.""" super().__init__(device_url, coordinator) - self._attr_supported_color_modes = set() + self._attr_supported_color_modes: set[ColorMode] = set() if self.executor.has_command(OverkizCommand.SET_RGB): - self._attr_supported_color_modes.add(COLOR_MODE_RGB) + self._attr_supported_color_modes.add(ColorMode.RGB) if self.executor.has_command(OverkizCommand.SET_INTENSITY): - self._attr_supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) if not self.supported_color_modes: - self._attr_supported_color_modes = {COLOR_MODE_ONOFF} + self._attr_supported_color_modes = {ColorMode.ONOFF} @property def is_on(self) -> bool: diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 3409c06be26..c81de1e6139 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -3,7 +3,7 @@ "name": "Overkiz (by Somfy)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", - "requirements": ["pyoverkiz==1.3.14"], + "requirements": ["pyoverkiz==1.4.0"], "zeroconf": [ { "type": "_kizbox._tcp.local.", diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index 40d04b9bf71..e6e64162dd8 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -28,6 +28,8 @@ class OverkizNumberDescriptionMixin: class OverkizNumberDescription(NumberEntityDescription, OverkizNumberDescriptionMixin): """Class to describe an Overkiz number.""" + inverted: bool = False + NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ # Cover: My Position (0 - 100) @@ -76,6 +78,14 @@ NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ max_value=15, entity_category=EntityCategory.CONFIG, ), + # DimmerExteriorHeating (Somfy Terrace Heater) (0 - 100) + # Needs to be inverted since 100 = off, 0 = on + OverkizNumberDescription( + key=OverkizState.CORE_LEVEL, + icon="mdi:patio-heater", + command=OverkizCommand.SET_LEVEL, + inverted=True, + ), ] SUPPORTED_STATES = {description.key: description for description in NUMBER_DESCRIPTIONS} @@ -119,12 +129,18 @@ class OverkizNumber(OverkizDescriptiveEntity, NumberEntity): def value(self) -> float | None: """Return the entity value to represent the entity state.""" if state := self.device.states.get(self.entity_description.key): + if self.entity_description.inverted: + return self._attr_max_value - cast(float, state.value) + return cast(float, state.value) return None async def async_set_value(self, value: float) -> None: """Set new value.""" + if self.entity_description.inverted: + value = self._attr_max_value - value + await self.executor.async_execute_command( self.entity_description.command, value ) diff --git a/homeassistant/components/overkiz/siren.py b/homeassistant/components/overkiz/siren.py index 8f9d2d6167f..02736f6f50a 100644 --- a/homeassistant/components/overkiz/siren.py +++ b/homeassistant/components/overkiz/siren.py @@ -4,13 +4,8 @@ from typing import Any from pyoverkiz.enums import OverkizState from pyoverkiz.enums.command import OverkizCommand, OverkizCommandParam -from homeassistant.components.siren import SirenEntity -from homeassistant.components.siren.const import ( - ATTR_DURATION, - SUPPORT_DURATION, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, -) +from homeassistant.components.siren import SirenEntity, SirenEntityFeature +from homeassistant.components.siren.const import ATTR_DURATION from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -38,7 +33,11 @@ async def async_setup_entry( class OverkizSiren(OverkizEntity, SirenEntity): """Representation an Overkiz Siren.""" - _attr_supported_features = SUPPORT_TURN_OFF | SUPPORT_TURN_ON | SUPPORT_DURATION + _attr_supported_features = ( + SirenEntityFeature.TURN_OFF + | SirenEntityFeature.TURN_ON + | SirenEntityFeature.DURATION + ) @property def is_on(self) -> bool: diff --git a/homeassistant/components/overkiz/translations/ca.json b/homeassistant/components/overkiz/translations/ca.json index 1e1bccd1fb7..d3a5a38edc3 100644 --- a/homeassistant/components/overkiz/translations/ca.json +++ b/homeassistant/components/overkiz/translations/ca.json @@ -9,6 +9,7 @@ "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "server_in_maintenance": "El servidor est\u00e0 inoperatiu per manteniment", + "too_many_attempts": "Massa intents amb un 'token' inv\u00e0lid, bloquejat temporalment", "too_many_requests": "Massa sol\u00b7licituds, torna-ho a provar m\u00e9s tard", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/overkiz/translations/de.json b/homeassistant/components/overkiz/translations/de.json index 84d09f9116b..09cce8ea63f 100644 --- a/homeassistant/components/overkiz/translations/de.json +++ b/homeassistant/components/overkiz/translations/de.json @@ -9,6 +9,7 @@ "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "server_in_maintenance": "Server ist wegen Wartungsarbeiten au\u00dfer Betrieb", + "too_many_attempts": "Zu viele Versuche mit einem ung\u00fcltigen Token, vor\u00fcbergehend gesperrt", "too_many_requests": "Zu viele Anfragen, versuche es sp\u00e4ter erneut.", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/overkiz/translations/el.json b/homeassistant/components/overkiz/translations/el.json index 9f7ad60cb09..924ecc3219d 100644 --- a/homeassistant/components/overkiz/translations/el.json +++ b/homeassistant/components/overkiz/translations/el.json @@ -9,6 +9,7 @@ "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "server_in_maintenance": "\u039f \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03c4\u03cc\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7", + "too_many_attempts": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b5\u03c2 \u03bc\u03b5 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc, \u03c0\u03c1\u03bf\u03c3\u03c9\u03c1\u03b9\u03bd\u03ac \u03b1\u03c0\u03bf\u03ba\u03bb\u03b5\u03b9\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2", "too_many_requests": "\u03a0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03bb\u03ac \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1, \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, diff --git a/homeassistant/components/overkiz/translations/et.json b/homeassistant/components/overkiz/translations/et.json index c19d9c39ca9..3cbfcb6af80 100644 --- a/homeassistant/components/overkiz/translations/et.json +++ b/homeassistant/components/overkiz/translations/et.json @@ -9,6 +9,7 @@ "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamine nurjus", "server_in_maintenance": "Server on hoolduse t\u00f5ttu maas", + "too_many_attempts": "Liiga palju katseid kehtetu v\u00f5tmega, ajutiselt keelatud", "too_many_requests": "Liiga palju p\u00e4ringuid, proovi hiljem uuesti", "unknown": "Ootamatu t\u00f5rge" }, diff --git a/homeassistant/components/overkiz/translations/fr.json b/homeassistant/components/overkiz/translations/fr.json index f6a56db80a5..c919301d541 100644 --- a/homeassistant/components/overkiz/translations/fr.json +++ b/homeassistant/components/overkiz/translations/fr.json @@ -9,6 +9,7 @@ "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification non valide", "server_in_maintenance": "Le serveur est ferm\u00e9 pour maintenance", + "too_many_attempts": "Trop de tentatives avec un jeton non valide\u00a0: banni temporairement", "too_many_requests": "Trop de demandes, r\u00e9essayez plus tard.", "unknown": "Erreur inattendue" }, diff --git a/homeassistant/components/overkiz/translations/hu.json b/homeassistant/components/overkiz/translations/hu.json index ef7cacaaf96..b6810749bd3 100644 --- a/homeassistant/components/overkiz/translations/hu.json +++ b/homeassistant/components/overkiz/translations/hu.json @@ -9,6 +9,7 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "server_in_maintenance": "A szerver karbantart\u00e1s miatt nem el\u00e9rhet\u0151", + "too_many_attempts": "T\u00fal sok pr\u00f3b\u00e1lkoz\u00e1s \u00e9rv\u00e9nytelen tokennel, ideiglenesen kitiltva", "too_many_requests": "T\u00fal sok a k\u00e9r\u00e9s, pr\u00f3b\u00e1lja meg k\u00e9s\u0151bb \u00fajra.", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, diff --git a/homeassistant/components/overkiz/translations/id.json b/homeassistant/components/overkiz/translations/id.json index becbae65f9e..c58b66b10a7 100644 --- a/homeassistant/components/overkiz/translations/id.json +++ b/homeassistant/components/overkiz/translations/id.json @@ -9,6 +9,7 @@ "cannot_connect": "Gagal terhubung", "invalid_auth": "Autentikasi tidak valid", "server_in_maintenance": "Server sedang dalam masa pemeliharaan", + "too_many_attempts": "Terlalu banyak percobaan dengan token yang tidak valid, untuk sementara diblokir", "too_many_requests": "Terlalu banyak permintaan, coba lagi nanti.", "unknown": "Kesalahan yang tidak diharapkan" }, diff --git a/homeassistant/components/overkiz/translations/it.json b/homeassistant/components/overkiz/translations/it.json index 00898513f1f..3dcc4e94e10 100644 --- a/homeassistant/components/overkiz/translations/it.json +++ b/homeassistant/components/overkiz/translations/it.json @@ -9,6 +9,7 @@ "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "server_in_maintenance": "Il server \u00e8 inattivo per manutenzione", + "too_many_attempts": "Troppi tentativi con un token non valido, temporaneamente bandito", "too_many_requests": "Troppe richieste, riprova pi\u00f9 tardi.", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/overkiz/translations/ja.json b/homeassistant/components/overkiz/translations/ja.json index e978a8f36f2..357408847fd 100644 --- a/homeassistant/components/overkiz/translations/ja.json +++ b/homeassistant/components/overkiz/translations/ja.json @@ -9,6 +9,7 @@ "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", "server_in_maintenance": "\u30e1\u30f3\u30c6\u30ca\u30f3\u30b9\u306e\u305f\u3081\u30b5\u30fc\u30d0\u30fc\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u307e\u3059", + "too_many_attempts": "\u7121\u52b9\u306a\u30c8\u30fc\u30af\u30f3\u306b\u3088\u308b\u8a66\u884c\u56de\u6570\u304c\u591a\u3059\u304e\u305f\u305f\u3081\u3001\u4e00\u6642\u7684\u306b\u7981\u6b62\u3055\u308c\u307e\u3057\u305f\u3002", "too_many_requests": "\u30ea\u30af\u30a8\u30b9\u30c8\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u3057\u3070\u3089\u304f\u3057\u3066\u304b\u3089\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, diff --git a/homeassistant/components/overkiz/translations/nl.json b/homeassistant/components/overkiz/translations/nl.json index d33dd5bb44c..5057f12afa6 100644 --- a/homeassistant/components/overkiz/translations/nl.json +++ b/homeassistant/components/overkiz/translations/nl.json @@ -9,6 +9,7 @@ "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "server_in_maintenance": "Server is offline wegens onderhoud", + "too_many_attempts": "Te veel pogingen met een ongeldig token, tijdelijk verbannen", "too_many_requests": "Te veel verzoeken, probeer het later opnieuw.", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/overkiz/translations/no.json b/homeassistant/components/overkiz/translations/no.json index ed691aa388f..e0d52e0fc0e 100644 --- a/homeassistant/components/overkiz/translations/no.json +++ b/homeassistant/components/overkiz/translations/no.json @@ -9,6 +9,7 @@ "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", "server_in_maintenance": "serveren er nede for vedlikehold", + "too_many_attempts": "For mange fors\u00f8k med et ugyldig token, midlertidig utestengt", "too_many_requests": "For mange foresp\u00f8rsler. Pr\u00f8v igjen senere", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/overkiz/translations/pl.json b/homeassistant/components/overkiz/translations/pl.json index 6881d7edb5b..7044dc717fd 100644 --- a/homeassistant/components/overkiz/translations/pl.json +++ b/homeassistant/components/overkiz/translations/pl.json @@ -9,6 +9,7 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", "server_in_maintenance": "Serwer wy\u0142\u0105czony z powodu przerwy technicznej", + "too_many_attempts": "Zbyt wiele pr\u00f3b z nieprawid\u0142owym tokenem, konto tymczasowo zablokowane", "too_many_requests": "Zbyt wiele \u017c\u0105da\u0144, spr\u00f3buj ponownie p\u00f3\u017aniej.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, diff --git a/homeassistant/components/overkiz/translations/pt-BR.json b/homeassistant/components/overkiz/translations/pt-BR.json index 2f2c335ebd5..123b2a83d42 100644 --- a/homeassistant/components/overkiz/translations/pt-BR.json +++ b/homeassistant/components/overkiz/translations/pt-BR.json @@ -9,6 +9,7 @@ "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "server_in_maintenance": "O servidor est\u00e1 fora de servi\u00e7o para manuten\u00e7\u00e3o", + "too_many_attempts": "Muitas tentativas com um token inv\u00e1lido, banido temporariamente", "too_many_requests": "Muitas solicita\u00e7\u00f5es, tente novamente mais tarde", "unknown": "Erro inesperado" }, diff --git a/homeassistant/components/overkiz/translations/ru.json b/homeassistant/components/overkiz/translations/ru.json index 611c4784902..53f08e1dbd6 100644 --- a/homeassistant/components/overkiz/translations/ru.json +++ b/homeassistant/components/overkiz/translations/ru.json @@ -9,6 +9,7 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "server_in_maintenance": "\u0421\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u0441\u0432\u044f\u0437\u0438 \u0441 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u0435\u043c.", + "too_many_attempts": "\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0441 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0442\u043e\u043a\u0435\u043d\u043e\u043c, \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e.", "too_many_requests": "\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, diff --git a/homeassistant/components/overkiz/translations/tr.json b/homeassistant/components/overkiz/translations/tr.json index 8e6a232db7f..1d04fbbd3cc 100644 --- a/homeassistant/components/overkiz/translations/tr.json +++ b/homeassistant/components/overkiz/translations/tr.json @@ -9,6 +9,7 @@ "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "server_in_maintenance": "Sunucu bak\u0131m nedeniyle kapal\u0131", + "too_many_attempts": "Ge\u00e7ersiz anahtarla \u00e7ok fazla deneme, ge\u00e7ici olarak yasakland\u0131", "too_many_requests": "\u00c7ok fazla istek var, daha sonra tekrar deneyin", "unknown": "Beklenmeyen hata" }, diff --git a/homeassistant/components/overkiz/translations/zh-Hant.json b/homeassistant/components/overkiz/translations/zh-Hant.json index 04c2fc2b07a..ab265301761 100644 --- a/homeassistant/components/overkiz/translations/zh-Hant.json +++ b/homeassistant/components/overkiz/translations/zh-Hant.json @@ -9,6 +9,7 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "server_in_maintenance": "\u4f3a\u670d\u5668\u7dad\u8b77\u4e2d", + "too_many_attempts": "\u4f7f\u7528\u7121\u6548\u6b0a\u6756\u5617\u8a66\u6b21\u6578\u904e\u591a\uff0c\u66ab\u6642\u906d\u5230\u5c01\u9396", "too_many_requests": "\u8acb\u6c42\u6b21\u6578\u904e\u591a\uff0c\u8acb\u7a0d\u5f8c\u91cd\u8a66\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/owntracks/translations/cs.json b/homeassistant/components/owntracks/translations/cs.json index d242a1275f1..a564efb190f 100644 --- a/homeassistant/components/owntracks/translations/cs.json +++ b/homeassistant/components/owntracks/translations/cs.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nen\u00ed p\u0159ipojeno k Home Assistant Cloud.", "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, "create_entry": { diff --git a/homeassistant/components/p1_monitor/translations/hu.json b/homeassistant/components/p1_monitor/translations/hu.json index f9025022c6d..b0c30613234 100644 --- a/homeassistant/components/p1_monitor/translations/hu.json +++ b/homeassistant/components/p1_monitor/translations/hu.json @@ -8,7 +8,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "\u00c1ll\u00edtsa be a P1 monitort az Otthoni asszisztenssel val\u00f3 integr\u00e1ci\u00f3hoz." } diff --git a/homeassistant/components/panasonic_bluray/media_player.py b/homeassistant/components/panasonic_bluray/media_player.py index 1e74c55a423..d87d2a8efc5 100644 --- a/homeassistant/components/panasonic_bluray/media_player.py +++ b/homeassistant/components/panasonic_bluray/media_player.py @@ -6,13 +6,10 @@ from datetime import timedelta from panacotta import PanasonicBD import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -31,9 +28,6 @@ DEFAULT_NAME = "Panasonic Blu-Ray" SCAN_INTERVAL = timedelta(seconds=30) -SUPPORT_PANASONIC_BD = ( - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY | SUPPORT_STOP | SUPPORT_PAUSE -) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -59,6 +53,14 @@ def setup_platform( class PanasonicBluRay(MediaPlayerEntity): """Representation of a Panasonic Blu-ray device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PAUSE + ) + def __init__(self, ip, name): """Initialize the Panasonic Blue-ray device.""" self._device = PanasonicBD(ip) @@ -83,11 +85,6 @@ class PanasonicBluRay(MediaPlayerEntity): """Return _state variable, containing the appropriate constant.""" return self._state - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_PANASONIC_BD - @property def media_duration(self): """Duration of current playing media in seconds.""" diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py index f9cff28f6ff..fd44c2853f1 100644 --- a/homeassistant/components/panasonic_viera/media_player.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -9,25 +9,12 @@ from homeassistant.components import media_source from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_URL, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_URL from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant @@ -45,21 +32,6 @@ from .const import ( DOMAIN, ) -SUPPORT_VIERATV = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_BROWSE_MEDIA -) - _LOGGER = logging.getLogger(__name__) @@ -83,6 +55,21 @@ async def async_setup_entry( class PanasonicVieraTVEntity(MediaPlayerEntity): """Representation of a Panasonic Viera TV.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) + def __init__(self, remote, name, device_info): """Initialize the entity.""" self._remote = remote @@ -138,11 +125,6 @@ class PanasonicVieraTVEntity(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._remote.muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_VIERATV - async def async_update(self): """Retrieve the latest data.""" await self._remote.async_update() diff --git a/homeassistant/components/panasonic_viera/translations/hu.json b/homeassistant/components/panasonic_viera/translations/hu.json index e373a352a45..7fd4d2524da 100644 --- a/homeassistant/components/panasonic_viera/translations/hu.json +++ b/homeassistant/components/panasonic_viera/translations/hu.json @@ -20,7 +20,7 @@ "user": { "data": { "host": "IP c\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Adja meg a Panasonic Viera TV-hez tartoz\u00f3 IP c\u00edmet", "title": "A TV be\u00e1ll\u00edt\u00e1sa" diff --git a/homeassistant/components/pandora/media_player.py b/homeassistant/components/pandora/media_player.py index 7866b99221e..542d749c573 100644 --- a/homeassistant/components/pandora/media_player.py +++ b/homeassistant/components/pandora/media_player.py @@ -11,16 +11,11 @@ import signal import pexpect from homeassistant import util -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, SERVICE_MEDIA_NEXT_TRACK, @@ -39,16 +34,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) -# SUPPORT_VOLUME_SET is close to available but we need volume up/down -# controls in the GUI. -PANDORA_SUPPORT = ( - SUPPORT_PAUSE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_NEXT_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY -) CMD_MAP = { SERVICE_MEDIA_NEXT_TRACK: "n", @@ -85,6 +70,17 @@ def setup_platform( class PandoraMediaPlayer(MediaPlayerEntity): """A media player that uses the Pianobar interface to Pandora.""" + # MediaPlayerEntityFeature.VOLUME_SET is close to available but we need volume up/down + # controls in the GUI. + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + ) + def __init__(self, name): """Initialize the Pandora device.""" self._name = name @@ -172,11 +168,6 @@ class PandoraMediaPlayer(MediaPlayerEntity): self._send_pianobar_command(SERVICE_MEDIA_NEXT_TRACK) self.schedule_update_ha_state() - @property - def supported_features(self): - """Flag media player features that are supported.""" - return PANDORA_SUPPORT - @property def source(self): """Name of the current input source.""" diff --git a/homeassistant/components/peco/__init__.py b/homeassistant/components/peco/__init__.py index 068a56c9bb8..6f88bf36c50 100644 --- a/homeassistant/components/peco/__init__.py +++ b/homeassistant/components/peco/__init__.py @@ -2,10 +2,11 @@ from __future__ import annotations import asyncio +from dataclasses import dataclass from datetime import timedelta from typing import Final -from peco import BadJSONError, HttpError, OutageResults, PecoOutageApi +from peco import AlertResults, BadJSONError, HttpError, OutageResults, PecoOutageApi from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -18,6 +19,14 @@ from .const import CONF_COUNTY, DOMAIN, LOGGER, SCAN_INTERVAL PLATFORMS: Final = [Platform.SENSOR] +@dataclass +class PECOCoordinatorData: + """Something to hold the data for PECO.""" + + outages: OutageResults + alerts: AlertResults + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up PECO Outage Counter from a config entry.""" @@ -25,14 +34,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api = PecoOutageApi() county: str = entry.data[CONF_COUNTY] - async def async_update_data() -> OutageResults: + async def async_update_data() -> PECOCoordinatorData: """Fetch data from API.""" try: - data: OutageResults = ( + outages: OutageResults = ( await api.get_outage_totals(websession) if county == "TOTAL" else await api.get_outage_count(county, websession) ) + alerts: AlertResults = await api.get_map_alerts(websession) + data = PECOCoordinatorData(outages, alerts) except HttpError as err: raise UpdateFailed(f"Error fetching data: {err}") from err except BadJSONError as err: diff --git a/homeassistant/components/peco/const.py b/homeassistant/components/peco/const.py index 18ed2c28b35..b0198ac8761 100644 --- a/homeassistant/components/peco/const.py +++ b/homeassistant/components/peco/const.py @@ -16,3 +16,4 @@ COUNTY_LIST: Final = [ CONFIG_FLOW_COUNTIES: Final = [{county: county.capitalize()} for county in COUNTY_LIST] SCAN_INTERVAL: Final = 9 CONF_COUNTY: Final = "county" +ATTR_CONTENT: Final = "content" diff --git a/homeassistant/components/peco/manifest.json b/homeassistant/components/peco/manifest.json index 7f41f2c0417..ef8a96c191b 100644 --- a/homeassistant/components/peco/manifest.json +++ b/homeassistant/components/peco/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/peco", "codeowners": ["@IceBotYT"], "iot_class": "cloud_polling", - "requirements": ["peco==0.0.25"] + "requirements": ["peco==0.0.29"] } diff --git a/homeassistant/components/peco/sensor.py b/homeassistant/components/peco/sensor.py index bef5040a6f6..dfd354f5c03 100644 --- a/homeassistant/components/peco/sensor.py +++ b/homeassistant/components/peco/sensor.py @@ -5,8 +5,6 @@ from collections.abc import Callable from dataclasses import dataclass from typing import Final -from peco import OutageResults - from homeassistant.components.sensor import ( SensorEntity, SensorEntityDescription, @@ -21,14 +19,16 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, ) -from .const import CONF_COUNTY, DOMAIN +from . import PECOCoordinatorData +from .const import ATTR_CONTENT, CONF_COUNTY, DOMAIN @dataclass class PECOSensorEntityDescriptionMixin: """Mixin for required keys.""" - value_fn: Callable[[OutageResults], int] + value_fn: Callable[[PECOCoordinatorData], int | str] + attribute_fn: Callable[[PECOCoordinatorData], dict[str, str]] @dataclass @@ -43,23 +43,42 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( PECOSensorEntityDescription( key="customers_out", name="Customers Out", - value_fn=lambda data: int(data.customers_out), + value_fn=lambda data: int(data.outages.customers_out), + attribute_fn=lambda data: {}, + icon="mdi:power-plug-off", + state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( key="percent_customers_out", name="Percent Customers Out", native_unit_of_measurement=PERCENTAGE, - value_fn=lambda data: int(data.percent_customers_out), + value_fn=lambda data: int(data.outages.percent_customers_out), + attribute_fn=lambda data: {}, + icon="mdi:power-plug-off", + state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( key="outage_count", name="Outage Count", - value_fn=lambda data: int(data.outage_count), + value_fn=lambda data: int(data.outages.outage_count), + attribute_fn=lambda data: {}, + icon="mdi:power-plug-off", + state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( key="customers_served", name="Customers Served", - value_fn=lambda data: int(data.customers_served), + value_fn=lambda data: int(data.outages.customers_served), + attribute_fn=lambda data: {}, + icon="mdi:power-plug-off", + state_class=SensorStateClass.MEASUREMENT, + ), + PECOSensorEntityDescription( + key="map_alert", + name="Map Alert", + value_fn=lambda data: str(data.alerts.alert_title), + attribute_fn=lambda data: {ATTR_CONTENT: data.alerts.alert_content}, + icon="mdi:alert", ), ) @@ -80,18 +99,18 @@ async def async_setup_entry( return -class PecoSensor(CoordinatorEntity[DataUpdateCoordinator[OutageResults]], SensorEntity): +class PecoSensor( + CoordinatorEntity[DataUpdateCoordinator[PECOCoordinatorData]], SensorEntity +): """PECO outage counter sensor.""" - _attr_state_class = SensorStateClass.MEASUREMENT - _attr_icon: str = "mdi:power-plug-off" entity_description: PECOSensorEntityDescription def __init__( self, description: PECOSensorEntityDescription, county: str, - coordinator: DataUpdateCoordinator[OutageResults], + coordinator: DataUpdateCoordinator[PECOCoordinatorData], ) -> None: """Initialize the sensor.""" super().__init__(coordinator) @@ -100,6 +119,11 @@ class PecoSensor(CoordinatorEntity[DataUpdateCoordinator[OutageResults]], Sensor self.entity_description = description @property - def native_value(self) -> int: + def native_value(self) -> int | str: """Return the value of the sensor.""" return self.entity_description.value_fn(self.coordinator.data) + + @property + def extra_state_attributes(self) -> dict[str, str]: + """Return state attributes for the sensor.""" + return self.entity_description.attribute_fn(self.coordinator.data) diff --git a/homeassistant/components/peco/translations/bg.json b/homeassistant/components/peco/translations/bg.json new file mode 100644 index 00000000000..80a7cc489a9 --- /dev/null +++ b/homeassistant/components/peco/translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u0423\u0441\u043b\u0443\u0433\u0430\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/ca.json b/homeassistant/components/peco/translations/ca.json new file mode 100644 index 00000000000..1cca8a08a7f --- /dev/null +++ b/homeassistant/components/peco/translations/ca.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "El servei ja est\u00e0 configurat" + }, + "step": { + "user": { + "data": { + "county": "Comtat" + }, + "description": "Trieu el teu comtat a continuaci\u00f3.", + "title": "Comptador d'interrupcions PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/cs.json b/homeassistant/components/peco/translations/cs.json new file mode 100644 index 00000000000..8440070c91a --- /dev/null +++ b/homeassistant/components/peco/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/de.json b/homeassistant/components/peco/translations/de.json new file mode 100644 index 00000000000..4eb10b3e5e9 --- /dev/null +++ b/homeassistant/components/peco/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, + "step": { + "user": { + "data": { + "county": "Bundesland" + }, + "description": "Bitte w\u00e4hle unten dein Bundesland aus.", + "title": "PECO-St\u00f6rungsz\u00e4hler" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/el.json b/homeassistant/components/peco/translations/el.json new file mode 100644 index 00000000000..6b47cc3ccc1 --- /dev/null +++ b/homeassistant/components/peco/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "step": { + "user": { + "data": { + "county": "\u039a\u03bf\u03bc\u03b7\u03c4\u03b5\u03af\u03b1" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03bf\u03bc\u03b7\u03c4\u03b5\u03af\u03b1 \u03c3\u03b1\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.", + "title": "\u039c\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae\u03c2 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ce\u03bd PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/en.json b/homeassistant/components/peco/translations/en.json index 6f7ff2b0b12..60483a1d65c 100644 --- a/homeassistant/components/peco/translations/en.json +++ b/homeassistant/components/peco/translations/en.json @@ -7,7 +7,9 @@ "user": { "data": { "county": "County" - } + }, + "description": "Please choose your county below.", + "title": "PECO Outage Counter" } } } diff --git a/homeassistant/components/peco/translations/et.json b/homeassistant/components/peco/translations/et.json new file mode 100644 index 00000000000..117d0502ca3 --- /dev/null +++ b/homeassistant/components/peco/translations/et.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Teenus on juba h\u00e4\u00e4lestatud" + }, + "step": { + "user": { + "data": { + "county": "Maakond" + }, + "description": "Vali allpool oma maakond.", + "title": "PECO katkestuste loendur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/fr.json b/homeassistant/components/peco/translations/fr.json new file mode 100644 index 00000000000..e78f828f1af --- /dev/null +++ b/homeassistant/components/peco/translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9" + }, + "step": { + "user": { + "data": { + "county": "Comt\u00e9" + }, + "description": "Veuillez choisir votre comt\u00e9 ci-dessous.", + "title": "Compteur de pannes PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/he.json b/homeassistant/components/peco/translations/he.json new file mode 100644 index 00000000000..48a6eeeea33 --- /dev/null +++ b/homeassistant/components/peco/translations/he.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e9\u05d9\u05e8\u05d5\u05ea \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/hu.json b/homeassistant/components/peco/translations/hu.json new file mode 100644 index 00000000000..2e1e3482eb5 --- /dev/null +++ b/homeassistant/components/peco/translations/hu.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "step": { + "user": { + "data": { + "county": "Megye" + }, + "description": "K\u00e9rj\u00fck, v\u00e1lassza ki az al\u00e1bbiakban a megy\u00e9t.", + "title": "PECO kimarad\u00e1s sz\u00e1ml\u00e1l\u00f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/id.json b/homeassistant/components/peco/translations/id.json new file mode 100644 index 00000000000..0879348f593 --- /dev/null +++ b/homeassistant/components/peco/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "county": "Kota/kabupaten" + }, + "description": "Pilih kota/kabupaten Anda di bawah ini.", + "title": "Penghitung Pemadaman PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/it.json b/homeassistant/components/peco/translations/it.json new file mode 100644 index 00000000000..a7b76c27f43 --- /dev/null +++ b/homeassistant/components/peco/translations/it.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" + }, + "step": { + "user": { + "data": { + "county": "Provincia" + }, + "description": "Scegli la tua provincia qui sotto.", + "title": "Contatore interruzioni PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/ja.json b/homeassistant/components/peco/translations/ja.json new file mode 100644 index 00000000000..139e1f77bd9 --- /dev/null +++ b/homeassistant/components/peco/translations/ja.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u30b5\u30fc\u30d3\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "step": { + "user": { + "data": { + "county": "\u90e1" + }, + "description": "\u4ee5\u4e0b\u304b\u3089\u304a\u4f4f\u307e\u3044\u306e\u56fd\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "title": "PECO Outage\u30ab\u30a6\u30f3\u30bf\u30fc" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/nl.json b/homeassistant/components/peco/translations/nl.json new file mode 100644 index 00000000000..8e98f5077e4 --- /dev/null +++ b/homeassistant/components/peco/translations/nl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Service is al geconfigureerd" + }, + "step": { + "user": { + "data": { + "county": "County" + }, + "description": "Kies hieronder uw county", + "title": "PECO Uitval Teller" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/no.json b/homeassistant/components/peco/translations/no.json new file mode 100644 index 00000000000..00f9c025114 --- /dev/null +++ b/homeassistant/components/peco/translations/no.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert" + }, + "step": { + "user": { + "data": { + "county": "fylke" + }, + "description": "Velg ditt fylke nedenfor.", + "title": "PECO avbruddsteller" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/pl.json b/homeassistant/components/peco/translations/pl.json new file mode 100644 index 00000000000..bdd8a7ffb7c --- /dev/null +++ b/homeassistant/components/peco/translations/pl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" + }, + "step": { + "user": { + "data": { + "county": "Hrabstwo" + }, + "description": "Wybierz swoje hrabstwo poni\u017cej.", + "title": "Licznik awarii PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/pt-BR.json b/homeassistant/components/peco/translations/pt-BR.json new file mode 100644 index 00000000000..845baf5b479 --- /dev/null +++ b/homeassistant/components/peco/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado" + }, + "step": { + "user": { + "data": { + "county": "Munic\u00edpio" + }, + "description": "Por favor, escolha seu munic\u00edpio abaixo.", + "title": "Contador de Interrup\u00e7\u00e3o PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/ru.json b/homeassistant/components/peco/translations/ru.json new file mode 100644 index 00000000000..6c2c8d85cc6 --- /dev/null +++ b/homeassistant/components/peco/translations/ru.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "county": "\u041e\u043a\u0440\u0443\u0433" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043e\u043a\u0440\u0443\u0433.", + "title": "\u0421\u0447\u0435\u0442\u0447\u0438\u043a \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 PECO" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/tr.json b/homeassistant/components/peco/translations/tr.json new file mode 100644 index 00000000000..6a76e600789 --- /dev/null +++ b/homeassistant/components/peco/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "county": "\u0130l\u00e7e" + }, + "description": "L\u00fctfen a\u015fa\u011f\u0131dan il\u00e7enizi se\u00e7iniz.", + "title": "PECO Kesinti Sayac\u0131" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/zh-Hant.json b/homeassistant/components/peco/translations/zh-Hant.json new file mode 100644 index 00000000000..94318397f6f --- /dev/null +++ b/homeassistant/components/peco/translations/zh-Hant.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "step": { + "user": { + "data": { + "county": "\u7e23\u5e02" + }, + "description": "\u8acb\u9078\u64c7\u7e23\u5e02\u3002", + "title": "PECO Outage \u8a08\u6578\u5668" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/light.py b/homeassistant/components/philips_js/light.py index 21bb7199269..5a32d2954a4 100644 --- a/homeassistant/components/philips_js/light.py +++ b/homeassistant/components/philips_js/light.py @@ -1,6 +1,8 @@ """Component to integrate ambilight for TVs exposing the Joint Space API.""" from __future__ import annotations +from dataclasses import dataclass + from haphilipsjs import PhilipsTV from haphilipsjs.typing import AmbilightCurrentConfiguration @@ -8,12 +10,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_HS_COLOR, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -51,44 +50,54 @@ def _get_settings(style: AmbilightCurrentConfiguration): return None -def _parse_effect(effect: str): - style, _, algorithm = effect.partition(EFFECT_PARTITION) - if style == EFFECT_MODE: - return EFFECT_MODE, algorithm, None - algorithm, _, expert = algorithm.partition(EFFECT_PARTITION) - if expert: - return EFFECT_EXPERT, style, algorithm - return EFFECT_AUTO, style, algorithm +@dataclass +class AmbilightEffect: + """Data class describing the ambilight effect.""" + mode: str + style: str + algorithm: str | None = None -def _get_effect(mode: str, style: str, algorithm: str | None): - if mode == EFFECT_MODE: - return f"{EFFECT_MODE}{EFFECT_PARTITION}{style}" - if mode == EFFECT_EXPERT: - return f"{style}{EFFECT_PARTITION}{algorithm}{EFFECT_PARTITION}{EFFECT_EXPERT}" - return f"{style}{EFFECT_PARTITION}{algorithm}" + def is_on(self, powerstate) -> bool: + """Check whether the ambilight is considered on.""" + if self.mode in (EFFECT_AUTO, EFFECT_EXPERT): + if self.style in ("FOLLOW_VIDEO", "FOLLOW_AUDIO"): + return powerstate in ("On", None) + if self.style == "OFF": + return False + return True + if self.mode == EFFECT_MODE: + if self.style == "internal": + return powerstate in ("On", None) + return True -def _is_on(mode, style, powerstate): - if mode in (EFFECT_AUTO, EFFECT_EXPERT): - if style in ("FOLLOW_VIDEO", "FOLLOW_AUDIO"): - return powerstate in ("On", None) - if style == "OFF": - return False + return False + + def is_valid(self) -> bool: + """Validate the effect configuration.""" + if self.mode == EFFECT_EXPERT: + return self.style in EFFECT_EXPERT_STYLES return True - if mode == EFFECT_MODE: - if style == "internal": - return powerstate in ("On", None) - return True + @staticmethod + def from_str(effect_string: str) -> AmbilightEffect: + """Create AmbilightEffect object from string.""" + style, _, algorithm = effect_string.partition(EFFECT_PARTITION) + if style == EFFECT_MODE: + return AmbilightEffect(mode=EFFECT_MODE, style=algorithm, algorithm=None) + algorithm, _, expert = algorithm.partition(EFFECT_PARTITION) + if expert: + return AmbilightEffect(mode=EFFECT_EXPERT, style=style, algorithm=algorithm) + return AmbilightEffect(mode=EFFECT_AUTO, style=style, algorithm=algorithm) - return False - - -def _is_valid(mode, style): - if mode == EFFECT_EXPERT: - return style in EFFECT_EXPERT_STYLES - return True + def __str__(self) -> str: + """Get a string representation of the effect.""" + if self.mode == EFFECT_MODE: + return f"{EFFECT_MODE}{EFFECT_PARTITION}{self.style}" + if self.mode == EFFECT_EXPERT: + return f"{self.style}{EFFECT_PARTITION}{self.algorithm}{EFFECT_PARTITION}{EFFECT_EXPERT}" + return f"{self.style}{EFFECT_PARTITION}{self.algorithm}" def _get_cache_keys(device: PhilipsTV): @@ -137,12 +146,11 @@ class PhilipsTVLightEntity( self._hs = None self._brightness = None self._cache_keys = None + self._last_selected_effect: AmbilightEffect = None super().__init__(coordinator) - self._attr_supported_color_modes = [COLOR_MODE_HS, COLOR_MODE_ONOFF] - self._attr_supported_features = ( - SUPPORT_EFFECT | SUPPORT_COLOR | SUPPORT_BRIGHTNESS - ) + self._attr_supported_color_modes = {ColorMode.HS, ColorMode.ONOFF} + self._attr_supported_features = LightEntityFeature.EFFECT self._attr_name = f"{coordinator.system['name']} Ambilight" self._attr_unique_id = coordinator.unique_id self._attr_icon = "mdi:television-ambient-light" @@ -160,67 +168,67 @@ class PhilipsTVLightEntity( def _calculate_effect_list(self): """Calculate an effect list based on current status.""" - effects = [] + effects: list[AmbilightEffect] = [] effects.extend( - _get_effect(EFFECT_AUTO, style, setting) + AmbilightEffect(mode=EFFECT_AUTO, style=style, algorithm=setting) for style, data in self._tv.ambilight_styles.items() - if _is_valid(EFFECT_AUTO, style) - and _is_on(EFFECT_AUTO, style, self._tv.powerstate) for setting in data.get("menuSettings", []) ) effects.extend( - _get_effect(EFFECT_EXPERT, style, algorithm) + AmbilightEffect(mode=EFFECT_EXPERT, style=style, algorithm=algorithm) for style, data in self._tv.ambilight_styles.items() - if _is_valid(EFFECT_EXPERT, style) - and _is_on(EFFECT_EXPERT, style, self._tv.powerstate) for algorithm in data.get("algorithms", []) ) effects.extend( - _get_effect(EFFECT_MODE, style, None) + AmbilightEffect(mode=EFFECT_MODE, style=style) for style in self._tv.ambilight_modes - if _is_valid(EFFECT_MODE, style) - and _is_on(EFFECT_MODE, style, self._tv.powerstate) ) - return sorted(effects) + filtered_effects = [ + str(effect) + for effect in effects + if effect.is_valid() and effect.is_on(self._tv.powerstate) + ] - def _calculate_effect(self): + return sorted(filtered_effects) + + def _calculate_effect(self) -> AmbilightEffect: """Return the current effect.""" current = self._tv.ambilight_current_configuration if current and self._tv.ambilight_mode != "manual": if current["isExpert"]: if settings := _get_settings(current): - return _get_effect( + return AmbilightEffect( EFFECT_EXPERT, current["styleName"], settings["algorithm"] ) - return _get_effect(EFFECT_EXPERT, current["styleName"], None) + return AmbilightEffect(EFFECT_EXPERT, current["styleName"], None) - return _get_effect( + return AmbilightEffect( EFFECT_AUTO, current["styleName"], current.get("menuSetting", None) ) - return _get_effect(EFFECT_MODE, self._tv.ambilight_mode, None) + return AmbilightEffect(EFFECT_MODE, self._tv.ambilight_mode, None) @property - def color_mode(self): + def color_mode(self) -> ColorMode: """Return the current color mode.""" current = self._tv.ambilight_current_configuration if current and current["isExpert"]: - return COLOR_MODE_HS + return ColorMode.HS if self._tv.ambilight_mode in ["manual", "expert"]: - return COLOR_MODE_HS + return ColorMode.HS - return COLOR_MODE_ONOFF + return ColorMode.ONOFF @property def is_on(self): """Return if the light is turned on.""" if self._tv.on: - mode, style, _ = _parse_effect(self.effect) - return _is_on(mode, style, self._tv.powerstate) + effect = AmbilightEffect.from_str(self.effect) + return effect.is_on(self._tv.powerstate) return False @@ -231,21 +239,23 @@ class PhilipsTVLightEntity( if (cache_keys := _get_cache_keys(self._tv)) != self._cache_keys: self._cache_keys = cache_keys self._attr_effect_list = self._calculate_effect_list() - self._attr_effect = self._calculate_effect() + self._attr_effect = str(self._calculate_effect()) if current and current["isExpert"]: if settings := _get_settings(current): color = settings["color"] - mode, _, _ = _parse_effect(self._attr_effect) + effect = AmbilightEffect.from_str(self._attr_effect) + if effect.is_on(self._tv.powerstate): + self._last_selected_effect = effect - if mode == EFFECT_EXPERT and color: + if effect.mode == EFFECT_EXPERT and color: self._attr_hs_color = ( color["hue"] * 360.0 / 255.0, color["saturation"] * 100.0 / 255.0, ) self._attr_brightness = color["brightness"] - elif mode == EFFECT_MODE and self._tv.ambilight_cached: + elif effect.mode == EFFECT_MODE and self._tv.ambilight_cached: hsv_h, hsv_s, hsv_v = color_RGB_to_hsv( *_average_pixels(self._tv.ambilight_cached) ) @@ -261,7 +271,9 @@ class PhilipsTVLightEntity( self._update_from_coordinator() super()._handle_coordinator_update() - async def _set_ambilight_cached(self, algorithm, hs_color, brightness): + async def _set_ambilight_cached( + self, effect: AmbilightEffect, hs_color: tuple[float, float], brightness: int + ): """Set ambilight via the manual or expert mode.""" rgb = color_hsv_to_RGB(hs_color[0], hs_color[1], brightness * 100 / 255) @@ -274,21 +286,21 @@ class PhilipsTVLightEntity( if not await self._tv.setAmbilightCached(data): raise Exception("Failed to set ambilight color") - if algorithm != self._tv.ambilight_mode: - if not await self._tv.setAmbilightMode(algorithm): + if effect.style != self._tv.ambilight_mode: + if not await self._tv.setAmbilightMode(effect.style): raise Exception("Failed to set ambilight mode") async def _set_ambilight_expert_config( - self, style, algorithm, hs_color, brightness + self, effect: AmbilightEffect, hs_color: tuple[float, float], brightness: int ): """Set ambilight via current configuration.""" config: AmbilightCurrentConfiguration = { - "styleName": style, + "styleName": effect.style, "isExpert": True, } setting = { - "algorithm": algorithm, + "algorithm": effect.algorithm, "color": { "hue": round(hs_color[0] * 255.0 / 360.0), "saturation": round(hs_color[1] * 255.0 / 100.0), @@ -301,23 +313,23 @@ class PhilipsTVLightEntity( }, } - if style in ("FOLLOW_COLOR", "Lounge light"): + if effect.style in ("FOLLOW_COLOR", "Lounge light"): config["colorSettings"] = setting config["speed"] = 2 - elif style == "FOLLOW_AUDIO": + elif effect.style == "FOLLOW_AUDIO": config["audioSettings"] = setting config["tuning"] = 0 if not await self._tv.setAmbilightCurrentConfiguration(config): raise Exception("Failed to set ambilight mode") - async def _set_ambilight_config(self, style, algorithm): + async def _set_ambilight_config(self, effect: AmbilightEffect): """Set ambilight via current configuration.""" config: AmbilightCurrentConfiguration = { - "styleName": style, + "styleName": effect.style, "isExpert": False, - "menuSetting": algorithm, + "menuSetting": effect.algorithm, } if await self._tv.setAmbilightCurrentConfiguration(config) is False: @@ -327,35 +339,39 @@ class PhilipsTVLightEntity( """Turn the bulb on.""" brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color) - effect = kwargs.get(ATTR_EFFECT, self.effect) + attr_effect = kwargs.get(ATTR_EFFECT, self.effect) if not self._tv.on: raise Exception("TV is not available") - mode, style, setting = _parse_effect(effect) + effect = AmbilightEffect.from_str(attr_effect) - if not _is_on(mode, style, self._tv.powerstate): - mode = EFFECT_MODE - setting = None - if self._tv.powerstate in ("On", None): - style = "internal" + if effect.style == "OFF": + if self._last_selected_effect: + effect = self._last_selected_effect else: - style = "manual" + effect = AmbilightEffect(EFFECT_AUTO, "FOLLOW_VIDEO", "STANDARD") + + if not effect.is_on(self._tv.powerstate): + effect.mode = EFFECT_MODE + effect.algorithm = None + if self._tv.powerstate in ("On", None): + effect.style = "internal" + else: + effect.style = "manual" if brightness is None: brightness = 255 if hs_color is None: - hs_color = [0, 0] + hs_color = (0, 0) - if mode == EFFECT_MODE: - await self._set_ambilight_cached(style, hs_color, brightness) - elif mode == EFFECT_AUTO: - await self._set_ambilight_config(style, setting) - elif mode == EFFECT_EXPERT: - await self._set_ambilight_expert_config( - style, setting, hs_color, brightness - ) + if effect.mode == EFFECT_MODE: + await self._set_ambilight_cached(effect, hs_color, brightness) + elif effect.mode == EFFECT_AUTO: + await self._set_ambilight_config(effect) + elif effect.mode == EFFECT_EXPERT: + await self._set_ambilight_expert_config(effect, hs_color, brightness) self._update_from_coordinator() self.async_write_ha_state() @@ -369,7 +385,7 @@ class PhilipsTVLightEntity( if await self._tv.setAmbilightMode("internal") is False: raise Exception("Failed to set ambilight mode") - await self._set_ambilight_config("OFF", "") + await self._set_ambilight_config(AmbilightEffect(EFFECT_MODE, "OFF", "")) self._update_from_coordinator() self.async_write_ha_state() diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index 948ce8703a1..cd613306130 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -2,7 +2,7 @@ "domain": "philips_js", "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", - "requirements": ["ha-philipsjs==2.7.6"], + "requirements": ["ha-philipsjs==2.9.0"], "codeowners": ["@elupus"], "config_flow": true, "iot_class": "local_polling", diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 77a1d9dccd9..27cf41abd4f 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -7,6 +7,7 @@ from homeassistant.components.media_player import ( BrowseMedia, MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.components.media_player.const import ( MEDIA_CLASS_APP, @@ -16,19 +17,6 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_APPS, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_CHANNELS, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError from homeassistant.config_entries import ConfigEntry @@ -42,18 +30,18 @@ from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator from .const import DOMAIN SUPPORT_PHILIPS_JS = ( - SUPPORT_TURN_OFF - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_SELECT_SOURCE - | SUPPORT_NEXT_TRACK - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_BROWSE_MEDIA - | SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_STOP + MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP ) CONF_ON_ACTION = "turn_on_action" @@ -128,7 +116,7 @@ class PhilipsTVMediaPlayer( if self.coordinator.turn_on or ( self._tv.on and self._tv.powerstate is not None ): - supports |= SUPPORT_TURN_ON + supports |= MediaPlayerEntityFeature.TURN_ON return supports @property diff --git a/homeassistant/components/philips_js/switch.py b/homeassistant/components/philips_js/switch.py index a89c22f1850..bf1e83b0ec1 100644 --- a/homeassistant/components/philips_js/switch.py +++ b/homeassistant/components/philips_js/switch.py @@ -13,6 +13,9 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import PhilipsTVDataUpdateCoordinator from .const import DOMAIN +HUE_POWER_OFF = "Off" +HUE_POWER_ON = "On" + async def async_setup_entry( hass: HomeAssistant, @@ -26,6 +29,9 @@ async def async_setup_entry( async_add_entities([PhilipsTVScreenSwitch(coordinator)]) + if coordinator.api.json_feature_supported("ambilight", "Hue"): + async_add_entities([PhilipsTVAmbilightHueSwitch(coordinator)]) + class PhilipsTVScreenSwitch( CoordinatorEntity[PhilipsTVDataUpdateCoordinator], SwitchEntity @@ -70,3 +76,50 @@ class PhilipsTVScreenSwitch( async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" await self.coordinator.api.setScreenState("Off") + + +class PhilipsTVAmbilightHueSwitch( + CoordinatorEntity[PhilipsTVDataUpdateCoordinator], SwitchEntity +): + """A Philips TV Ambi+Hue switch.""" + + def __init__( + self, + coordinator: PhilipsTVDataUpdateCoordinator, + ) -> None: + """Initialize entity.""" + + super().__init__(coordinator) + + self._attr_name = f"{coordinator.system['name']} Ambilight+Hue" + self._attr_icon = "mdi:television-ambient-light" + self._attr_unique_id = f"{coordinator.unique_id}_ambi_hue" + self._attr_device_info = DeviceInfo( + identifiers={ + (DOMAIN, coordinator.unique_id), + } + ) + + @property + def available(self) -> bool: + """Return true if entity is available.""" + if not super().available: + return False + if not self.coordinator.api.on: + return False + return self.coordinator.api.powerstate == "On" + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + return self.coordinator.api.huelamp_power == HUE_POWER_ON + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + await self.coordinator.api.setHueLampPower(HUE_POWER_ON) + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + await self.coordinator.api.setHueLampPower(HUE_POWER_OFF) + self.async_write_ha_state() diff --git a/homeassistant/components/pi_hole/manifest.json b/homeassistant/components/pi_hole/manifest.json index cca92d9bcec..f35b7919251 100644 --- a/homeassistant/components/pi_hole/manifest.json +++ b/homeassistant/components/pi_hole/manifest.json @@ -3,7 +3,7 @@ "name": "Pi-hole", "documentation": "https://www.home-assistant.io/integrations/pi_hole", "requirements": ["hole==0.7.0"], - "codeowners": ["@fabaff", "@johnluetke", "@shenxn"], + "codeowners": ["@johnluetke", "@shenxn"], "config_flow": true, "iot_class": "local_polling", "loggers": ["hole"] diff --git a/homeassistant/components/pi_hole/translations/hu.json b/homeassistant/components/pi_hole/translations/hu.json index 71321c4cf85..e55c7d543e3 100644 --- a/homeassistant/components/pi_hole/translations/hu.json +++ b/homeassistant/components/pi_hole/translations/hu.json @@ -17,7 +17,7 @@ "api_key": "API kulcs", "host": "C\u00edm", "location": "Elhelyezked\u00e9s", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "port": "Port", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "statistics_only": "Csak statisztik\u00e1k", diff --git a/homeassistant/components/pilight/light.py b/homeassistant/components/pilight/light.py index 2838895159c..c96644f7ea7 100644 --- a/homeassistant/components/pilight/light.py +++ b/homeassistant/components/pilight/light.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_LIGHTS @@ -49,6 +49,9 @@ def setup_platform( class PilightLight(PilightBaseDevice, LightEntity): """Representation of a Pilight switch.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, hass, name, config): """Initialize a switch.""" super().__init__(hass, name, config) @@ -60,11 +63,6 @@ class PilightLight(PilightBaseDevice, LightEntity): """Return the brightness.""" return self._brightness - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - def turn_on(self, **kwargs): """Turn the switch on by calling pilight.send service with on code.""" # Update brightness only if provided as an argument. diff --git a/homeassistant/components/pioneer/media_player.py b/homeassistant/components/pioneer/media_player.py index f91a9754713..7e3931b1ae3 100644 --- a/homeassistant/components/pioneer/media_player.py +++ b/homeassistant/components/pioneer/media_player.py @@ -6,16 +6,10 @@ import telnetlib import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -39,16 +33,6 @@ DEFAULT_PORT = 23 # telnet default. Some Pioneer AVRs use 8102 DEFAULT_TIMEOUT = None DEFAULT_SOURCES: dict[str, str] = {} -SUPPORT_PIONEER = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY -) MAX_VOLUME = 185 MAX_SOURCE_NUMBERS = 60 @@ -86,6 +70,17 @@ def setup_platform( class PioneerDevice(MediaPlayerEntity): """Representation of a Pioneer device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + ) + def __init__(self, name, host, port, timeout, sources): """Initialize the Pioneer device.""" self._name = name @@ -200,11 +195,6 @@ class PioneerDevice(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_PIONEER - @property def source(self): """Return the current input source.""" diff --git a/homeassistant/components/pjlink/media_player.py b/homeassistant/components/pjlink/media_player.py index 842933a7fb5..73fb1888341 100644 --- a/homeassistant/components/pjlink/media_player.py +++ b/homeassistant/components/pjlink/media_player.py @@ -5,12 +5,10 @@ from pypjlink import MUTE_AUDIO, Projector from pypjlink.projector import ProjectorError import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -41,10 +39,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -SUPPORT_PJLINK = ( - SUPPORT_VOLUME_MUTE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE -) - def setup_platform( hass: HomeAssistant, @@ -80,6 +74,13 @@ def format_input_source(input_source_name, input_source_number): class PjLinkDevice(MediaPlayerEntity): """Representation of a PJLink device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, host, port, name, encoding, password): """Iinitialize the PJLink device.""" self._host = host @@ -160,11 +161,6 @@ class PjLinkDevice(MediaPlayerEntity): """Return all available input sources.""" return self._source_list - @property - def supported_features(self): - """Return projector supported features.""" - return SUPPORT_PJLINK - def turn_off(self): """Turn projector off.""" with self.projector() as projector: diff --git a/homeassistant/components/plaato/translations/cs.json b/homeassistant/components/plaato/translations/cs.json index 4d736d1c695..5e3c9682226 100644 --- a/homeassistant/components/plaato/translations/cs.json +++ b/homeassistant/components/plaato/translations/cs.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u00da\u010det je ji\u017e nastaven", + "cloud_not_connected": "Nen\u00ed p\u0159ipojeno k Home Assistant Cloud.", "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace.", "webhook_not_internet_accessible": "V\u00e1\u0161 Home Assistant mus\u00ed b\u00fdt p\u0159\u00edstupn\u00fd z internetu, aby mohl p\u0159ij\u00edmat zpr\u00e1vy webhook." }, diff --git a/homeassistant/components/plant/translations/zh-Hant.json b/homeassistant/components/plant/translations/zh-Hant.json index af06d09eef6..052086d04eb 100644 --- a/homeassistant/components/plant/translations/zh-Hant.json +++ b/homeassistant/components/plant/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "state": { "_": { - "ok": "\u5065\u5eb7", + "ok": "\u6b63\u5e38", "problem": "\u7570\u5e38" } }, diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 7baee63477a..8da67dafc3d 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -420,7 +420,6 @@ class PlexAuthorizationCallbackView(HomeAssistantView): async def get(self, request): """Receive authorization confirmation.""" - # pylint: disable=no-self-use hass = request.app["hass"] await hass.config_entries.flow.async_configure( flow_id=request.query["flow_id"], user_input=None diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index 1df624e265b..d65c01410c7 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -71,7 +71,7 @@ def browse_media( # noqa: C901 try: media_class = ITEM_TYPE_MEDIA_CLASS[item.type] except KeyError as err: - raise UnknownMediaType("Unknown type received: {item.type}") from err + raise UnknownMediaType(f"Unknown type received: {item.type}") from err payload = { "title": pretty_title(item, short_name), "media_class": media_class, diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 6e729618022..e5d420f46b0 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -7,20 +7,12 @@ import logging import plexapi.exceptions import requests.exceptions -from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + DOMAIN as MP_DOMAIN, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant, callback @@ -389,19 +381,21 @@ class PlexMediaPlayer(MediaPlayerEntity): """Flag media player features that are supported.""" if self.device and "playback" in self._device_protocol_capabilities: return ( - SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_STOP - | SUPPORT_SEEK - | SUPPORT_VOLUME_SET - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_VOLUME_MUTE - | SUPPORT_BROWSE_MEDIA + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.BROWSE_MEDIA ) - return SUPPORT_BROWSE_MEDIA | SUPPORT_PLAY_MEDIA + return ( + MediaPlayerEntityFeature.BROWSE_MEDIA | MediaPlayerEntityFeature.PLAY_MEDIA + ) def set_volume_level(self, volume): """Set volume level, range 0..1.""" diff --git a/homeassistant/components/plex/models.py b/homeassistant/components/plex/models.py index 0b77f450e7e..ffb6f791419 100644 --- a/homeassistant/components/plex/models.py +++ b/homeassistant/components/plex/models.py @@ -1,5 +1,4 @@ """Models to represent various Plex objects used in the integration.""" -from distutils.util import strtobool import logging from homeassistant.components.media_player.const import ( @@ -8,6 +7,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, ) +from homeassistant.helpers.template import result_as_boolean from homeassistant.util import dt as dt_util LIVE_TV_SECTION = "Live TV" @@ -162,7 +162,7 @@ class PlexMediaSearchResult: return offset * 1000 resume = self._params.get("resume", False) if isinstance(resume, str): - resume = bool(strtobool(resume)) + resume = result_as_boolean(resume) if resume: return self.media.viewOffset return 0 @@ -172,5 +172,5 @@ class PlexMediaSearchResult: """Return value of shuffle parameter.""" shuffle = self._params.get("shuffle", False) if isinstance(shuffle, str): - shuffle = bool(strtobool(shuffle)) + shuffle = result_as_boolean(shuffle) return shuffle diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index 06ecc081c9b..b7eff8043f8 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -170,6 +170,10 @@ def process_plex_payload( search_query = content.copy() shuffle = search_query.pop("shuffle", 0) + # Remove internal kwargs before passing copy to plexapi + for internal_key in ("resume", "offset"): + search_query.pop(internal_key, None) + media = plex_server.lookup_media(content_type, **search_query) if supports_playqueues and (isinstance(media, list) or shuffle): diff --git a/homeassistant/components/plex/translations/hu.json b/homeassistant/components/plex/translations/hu.json index d3aabedc58d..3be797cc04f 100644 --- a/homeassistant/components/plex/translations/hu.json +++ b/homeassistant/components/plex/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "Az \u00f6sszes \u00f6sszekapcsolt szerver m\u00e1r konfigur\u00e1lva van", "already_configured": "Ez a Plex szerver m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", "token_request_timeout": "Token k\u00e9r\u00e9sre sz\u00e1nt id\u0151 lej\u00e1rt", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" diff --git a/homeassistant/components/plex/view.py b/homeassistant/components/plex/view.py index 3cb7d40b2de..5780bd3c46b 100644 --- a/homeassistant/components/plex/view.py +++ b/homeassistant/components/plex/view.py @@ -22,7 +22,7 @@ class PlexImageView(HomeAssistantView): name = "api:plex:image" url = "/api/plex_image_proxy/{server_id}/{media_content_id}" - async def get( # pylint: disable=no-self-use + async def get( self, request: web.Request, server_id: str, diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 1e4b972e4d0..91a56d09ee5 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -6,14 +6,9 @@ from typing import Any from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -57,17 +52,17 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): self._attr_name = self.device.get("name") # Determine preset modes - self._attr_supported_features = SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE if presets := self.device.get("presets"): - self._attr_supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE self._attr_preset_modes = list(presets) # Determine hvac modes and current hvac mode - self._attr_hvac_modes = [HVAC_MODE_HEAT] + self._attr_hvac_modes = [HVACMode.HEAT] if self.coordinator.data.gateway.get("cooling_present"): - self._attr_hvac_modes.append(HVAC_MODE_COOL) + self._attr_hvac_modes.append(HVACMode.COOL) if self.device.get("available_schedules") != ["None"]: - self._attr_hvac_modes.append(HVAC_MODE_AUTO) + self._attr_hvac_modes.append(HVACMode.AUTO) self._attr_min_temp = self.device.get("lower_bound", DEFAULT_MIN_TEMP) self._attr_max_temp = self.device.get("upper_bound", DEFAULT_MAX_TEMP) @@ -86,31 +81,31 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): return self.device["sensors"].get("setpoint") @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return HVAC operation ie. heat, cool mode.""" if (mode := self.device.get("mode")) is None or mode not in self.hvac_modes: - return HVAC_MODE_HEAT - return mode + return HVACMode.HEAT + return HVACMode(mode) @property - def hvac_action(self) -> str: + def hvac_action(self) -> HVACAction: """Return the current running hvac operation if supported.""" # When control_state is present, prefer this data if "control_state" in self.device: if self.device.get("control_state") == "cooling": - return CURRENT_HVAC_COOL + return HVACAction.COOLING # Support preheating state as heating, until preheating is added as a separate state if self.device.get("control_state") in ["heating", "preheating"]: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING else: heater_central_data = self.coordinator.data.devices[ self.coordinator.data.gateway["heater_id"] ] if heater_central_data["binary_sensors"].get("heating_state"): - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if heater_central_data["binary_sensors"].get("cooling_state"): - return CURRENT_HVAC_COOL - return CURRENT_HVAC_IDLE + return HVACAction.COOLING + return HVACAction.IDLE @property def preset_mode(self) -> str | None: @@ -135,15 +130,15 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): await self.coordinator.api.set_temperature(self.device["location"], temperature) @plugwise_command - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the hvac mode.""" - if hvac_mode == HVAC_MODE_AUTO and not self.device.get("schedule_temperature"): + if hvac_mode == HVACMode.AUTO and not self.device.get("schedule_temperature"): raise ValueError("Cannot set HVAC mode to Auto: No schedule available") await self.coordinator.api.set_schedule_state( self.device["location"], self.device.get("last_used"), - "on" if hvac_mode == HVAC_MODE_AUTO else "off", + "on" if hvac_mode == HVACMode.AUTO else "off", ) @plugwise_command diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 5b5c79ba2b8..fbfdca00b41 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -92,8 +92,26 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): _properties = discovery_info.properties unique_id = discovery_info.hostname.split(".")[0] - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured({CONF_HOST: discovery_info.host}) + if config_entry := await self.async_set_unique_id(unique_id): + try: + await validate_gw_input( + self.hass, + { + CONF_HOST: discovery_info.host, + CONF_PORT: discovery_info.port, + CONF_USERNAME: config_entry.data[CONF_USERNAME], + CONF_PASSWORD: config_entry.data[CONF_PASSWORD], + }, + ) + except Exception: # pylint: disable=broad-except + self._abort_if_unique_id_configured() + else: + self._abort_if_unique_id_configured( + { + CONF_HOST: discovery_info.host, + CONF_PORT: discovery_info.port, + } + ) if DEFAULT_USERNAME not in unique_id: self._username = STRETCH_USERNAME diff --git a/homeassistant/components/plugwise/translations/ca.json b/homeassistant/components/plugwise/translations/ca.json index c29fa230519..85f7c357803 100644 --- a/homeassistant/components/plugwise/translations/ca.json +++ b/homeassistant/components/plugwise/translations/ca.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_setup": "Afegeix l'Adam en lloc de l'Anna; consulta la documentaci\u00f3 de la integraci\u00f3 Plugwise de Home Assistant per a m\u00e9s informaci\u00f3.", "unknown": "Error inesperat" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/de.json b/homeassistant/components/plugwise/translations/de.json index a5c11645d6f..f6ce5fb1b2d 100644 --- a/homeassistant/components/plugwise/translations/de.json +++ b/homeassistant/components/plugwise/translations/de.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_setup": "F\u00fcge deinen Adam anstelle deiner Anna hinzu. Weitere Informationen findest du in der Dokumentation zur Integration von Home Assistant Plugwise.", "unknown": "Unerwarteter Fehler" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json index a891a74d3ad..8407cb38cb1 100644 --- a/homeassistant/components/plugwise/translations/el.json +++ b/homeassistant/components/plugwise/translations/el.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_setup": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd Adam \u03c3\u03b1\u03c2 \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd Anna \u03c3\u03b1\u03c2, \u03b1\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Home Assistant Plugwise \u03b3\u03b9\u03b1 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/en.json b/homeassistant/components/plugwise/translations/en.json index 3ee5551fd83..616e450cb11 100644 --- a/homeassistant/components/plugwise/translations/en.json +++ b/homeassistant/components/plugwise/translations/en.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", + "invalid_setup": "Add your Adam instead of your Anna, see the Home Assistant Plugwise integration documentation for more information", "unknown": "Unexpected error" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/et.json b/homeassistant/components/plugwise/translations/et.json index 029b334f7c4..f863dd30b19 100644 --- a/homeassistant/components/plugwise/translations/et.json +++ b/homeassistant/components/plugwise/translations/et.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamine nurjus", + "invalid_setup": "Lisa oma Anna asemel oma Adam, lisateabe saamiseks vaata Home Assistant Plugwise'i sidumise dokumentatsiooni", "unknown": "Tundmatu viga" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/fr.json b/homeassistant/components/plugwise/translations/fr.json index c6098db43f0..baa6074e68a 100644 --- a/homeassistant/components/plugwise/translations/fr.json +++ b/homeassistant/components/plugwise/translations/fr.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification non valide", + "invalid_setup": "Ajoutez votre Adam au lieu de votre Anna\u00a0; consultez la documentation de l'int\u00e9gration Plugwise de Home Assistant pour plus d'informations", "unknown": "Erreur inattendue" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/hu.json b/homeassistant/components/plugwise/translations/hu.json index 4d588c84277..265a99186ba 100644 --- a/homeassistant/components/plugwise/translations/hu.json +++ b/homeassistant/components/plugwise/translations/hu.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_setup": "Adja hozz\u00e1 Adamot Anna helyett. Tov\u00e1bbi inform\u00e1ci\u00f3\u00e9rt tekintse meg a Home Assistant Plugwise integr\u00e1ci\u00f3s dokument\u00e1ci\u00f3j\u00e1t", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/id.json b/homeassistant/components/plugwise/translations/id.json index f3eeb0926ba..8878a4e775d 100644 --- a/homeassistant/components/plugwise/translations/id.json +++ b/homeassistant/components/plugwise/translations/id.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Gagal terhubung", "invalid_auth": "Autentikasi tidak valid", + "invalid_setup": "Tambahkan Adam Anda, alih-alih Anna. Baca dokumentasi integrasi Plugwise Home Assistant untuk informasi lebih lanjut", "unknown": "Kesalahan yang tidak diharapkan" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/it.json b/homeassistant/components/plugwise/translations/it.json index e60b91a106b..9e051a29e13 100644 --- a/homeassistant/components/plugwise/translations/it.json +++ b/homeassistant/components/plugwise/translations/it.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", + "invalid_setup": "Aggiungi il tuo Adam invece di Anna, consulta la documentazione sull'integrazione di Home Assistant Plugwise per ulteriori informazioni", "unknown": "Errore imprevisto" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/ja.json b/homeassistant/components/plugwise/translations/ja.json index cc3810edcd3..f15d62d38d1 100644 --- a/homeassistant/components/plugwise/translations/ja.json +++ b/homeassistant/components/plugwise/translations/ja.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "invalid_setup": "Anna\u306e\u4ee3\u308f\u308a\u306b\u3001Adam\u3092\u8ffd\u52a0\u3057\u307e\u3059\u3002\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001Home Assistant Plugwise\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 160cf182d3d..d295695016f 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", + "invalid_setup": "Voeg je Adam toe in plaats van je Anna, zie de Home Assistant Plugwise integratiedocumentatie voor meer informatie", "unknown": "Onverwachte fout" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/no.json b/homeassistant/components/plugwise/translations/no.json index 58ee9d4aed8..d8ce2d8956a 100644 --- a/homeassistant/components/plugwise/translations/no.json +++ b/homeassistant/components/plugwise/translations/no.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", + "invalid_setup": "Legg til din Adam i stedet for din Anna, se integrasjonsdokumentasjonen for Home Assistant Plugwise for mer informasjon", "unknown": "Uventet feil" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/pl.json b/homeassistant/components/plugwise/translations/pl.json index 5d0bdd0f4e6..c4a2efe95c3 100644 --- a/homeassistant/components/plugwise/translations/pl.json +++ b/homeassistant/components/plugwise/translations/pl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_setup": "Dodaj urz\u0105dzenie Adam zamiast Anna. Zobacz dokumentacj\u0119 integracji Plugwise dla Home Assistant, aby uzyska\u0107 wi\u0119cej informacji.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/pt-BR.json b/homeassistant/components/plugwise/translations/pt-BR.json index f53f6c7c379..35a08d93114 100644 --- a/homeassistant/components/plugwise/translations/pt-BR.json +++ b/homeassistant/components/plugwise/translations/pt-BR.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_setup": "Adicione seu Adam em vez de sua Anna, consulte a documenta\u00e7\u00e3o de integra\u00e7\u00e3o do Home Assistant Plugwise para obter mais informa\u00e7\u00f5es", "unknown": "Erro inesperado" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/ru.json b/homeassistant/components/plugwise/translations/ru.json index c7b28d5b415..62b7c1e27d3 100644 --- a/homeassistant/components/plugwise/translations/ru.json +++ b/homeassistant/components/plugwise/translations/ru.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_setup": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 Adam \u0432\u043c\u0435\u0441\u0442\u043e Anna. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439 \u043f\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Plugwise \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/tr.json b/homeassistant/components/plugwise/translations/tr.json index 4c752efbd4a..ae756bf15d4 100644 --- a/homeassistant/components/plugwise/translations/tr.json +++ b/homeassistant/components/plugwise/translations/tr.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_setup": "Anna'n\u0131z yerine Adam'\u0131n\u0131z\u0131 ekleyin, daha fazla bilgi i\u00e7in Home Assistant Plugwise entegrasyon belgelerine bak\u0131n", "unknown": "Beklenmeyen hata" }, "flow_title": "{name}", diff --git a/homeassistant/components/plugwise/translations/zh-Hant.json b/homeassistant/components/plugwise/translations/zh-Hant.json index 0a1e21a12ed..57a7add22e0 100644 --- a/homeassistant/components/plugwise/translations/zh-Hant.json +++ b/homeassistant/components/plugwise/translations/zh-Hant.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_setup": "\u65b0\u589e Adam \u800c\u975e Anna\u3001\u8acb\u53c3\u95b1 Home Assistant Plugwise \u6574\u5408\u8aaa\u660e\u6587\u4ef6\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "{name}", diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index f9983135e14..42fdcb538d8 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -8,8 +8,7 @@ from plumlightpad import Plum from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -115,11 +114,16 @@ class PlumLight(LightEntity): return self._brightness > 0 @property - def supported_features(self): + def color_mode(self) -> ColorMode: """Flag supported features.""" if self._load.dimmable: - return SUPPORT_BRIGHTNESS - return 0 + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[ColorMode]: + """Flag supported color modes.""" + return {self.color_mode} async def async_turn_on(self, **kwargs): """Turn the light on.""" @@ -136,6 +140,9 @@ class PlumLight(LightEntity): class GlowRing(LightEntity): """Representation of a Plum Lightpad dimmer glow ring.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, lightpad): """Initialize the light.""" self._lightpad = lightpad @@ -215,11 +222,6 @@ class GlowRing(LightEntity): """Return the crop-portrait icon representing the glow ring.""" return "mdi:crop-portrait" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR - async def async_turn_on(self, **kwargs): """Turn the light on.""" if ATTR_BRIGHTNESS in kwargs: diff --git a/homeassistant/components/point/alarm_control_panel.py b/homeassistant/components/point/alarm_control_panel.py index 41dc5b04436..46ea95ba927 100644 --- a/homeassistant/components/point/alarm_control_panel.py +++ b/homeassistant/components/point/alarm_control_panel.py @@ -1,8 +1,11 @@ """Support for Minut Point.""" import logging -from homeassistant.components.alarm_control_panel import DOMAIN, AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import SUPPORT_ALARM_ARM_AWAY +from homeassistant.components.alarm_control_panel import ( + DOMAIN, + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -46,6 +49,8 @@ async def async_setup_entry( class MinutPointAlarmControl(AlarmControlPanelEntity): """The platform class required by Home Assistant.""" + _attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY + def __init__(self, point_client, home_id): """Initialize the entity.""" self._client = point_client @@ -96,11 +101,6 @@ class MinutPointAlarmControl(AlarmControlPanelEntity): """Return state of the device.""" return EVENT_MAP.get(self._home["alarm_status"], STATE_ALARM_ARMED_AWAY) - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_AWAY - @property def changed_by(self): """Return the user the last change was triggered by.""" diff --git a/homeassistant/components/point/translations/hu.json b/homeassistant/components/point/translations/hu.json index c582bbfc7cd..27c35a1966f 100644 --- a/homeassistant/components/point/translations/hu.json +++ b/homeassistant/components/point/translations/hu.json @@ -11,12 +11,12 @@ "default": "Sikeres hiteles\u00edt\u00e9s" }, "error": { - "follow_link": "K\u00e9rem, k\u00f6vesse a hivatkoz\u00e1st \u00e9s hiteles\u00edtse mag\u00e1t miel\u0151tt megnyomn\u00e1 a K\u00fcld\u00e9s gombot", + "follow_link": "K\u00e9rem, k\u00f6vesse a hivatkoz\u00e1st \u00e9s hiteles\u00edtse mag\u00e1t miel\u0151tt folytatn\u00e1", "no_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" }, "step": { "auth": { - "description": "K\u00e9rem k\u00f6vesse az al\u00e1bbi linket \u00e9s a **Fogadja el** a hozz\u00e1f\u00e9r\u00e9st a Minut fi\u00f3kj\u00e1hoz, majd t\u00e9rjen vissza \u00e9s nyomja meg a **K\u00fcld\u00e9s ** gombot. \n\n [Link]({authorization_url})", + "description": "K\u00e9rem k\u00f6vesse az al\u00e1bbi linket \u00e9s a **Fogadja el** a hozz\u00e1f\u00e9r\u00e9st a Minut fi\u00f3kj\u00e1hoz, majd t\u00e9rjen vissza \u00e9s nyomja meg a **Mehet** gombot. \n\n [Link]({authorization_url})", "title": "Point hiteles\u00edt\u00e9se" }, "user": { @@ -24,7 +24,7 @@ "flow_impl": "Szolg\u00e1ltat\u00f3" }, "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?", - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/point/translations/it.json b/homeassistant/components/point/translations/it.json index 8a35f7acdf3..c527666e132 100644 --- a/homeassistant/components/point/translations/it.json +++ b/homeassistant/components/point/translations/it.json @@ -4,7 +4,7 @@ "already_setup": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "external_setup": "Point configurato correttamente da un altro flusso.", - "no_flows": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "no_flows": "Il componente non \u00e8 configurato. Segui la documentazione.", "unknown_authorize_url_generation": "Errore sconosciuto durante la generazione di un URL di autorizzazione." }, "create_entry": { diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index b2738bce4ac..b1791c4300e 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -9,7 +9,7 @@ POWERWALL_API_CHANGED: Final = "api_changed" POWERWALL_HTTP_SESSION: Final = "http_session" POWERWALL_LOGIN_FAILED_COUNT: Final = "login_failed_count" -UPDATE_INTERVAL = 5 +UPDATE_INTERVAL = 30 ATTR_FREQUENCY = "frequency" ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage" diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index 17f85254208..e68254918fb 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -15,7 +15,7 @@ "step": { "confirm_discovery": { "description": "M\u00f6chtest du {name} ({ip_address}) einrichten?", - "title": "Powerwall verbinden" + "title": "Stelle eine Verbindung zur Powerwall her" }, "reauth_confim": { "data": { diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 767f77e58bd..5d82ecda8e5 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -13,6 +13,9 @@ "flow_title": "Powerwall de Tesla ({ip_address})", "step": { "reauth_confim": { + "data": { + "password": "Contrase\u00f1a" + }, "title": "Reautorizar la powerwall" }, "user": { diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index 0f997dd41bd..978c19dc2ab 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -1,6 +1,5 @@ """The profiler integration.""" import asyncio -import cProfile from datetime import timedelta import logging import reprlib @@ -10,9 +9,6 @@ import time import traceback from typing import Any -from guppy import hpy -import objgraph -from pyprof2calltree import convert import voluptuous as vol from homeassistant.components import persistent_notification @@ -100,6 +96,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return f"Failed to serialize {type(obj)}" def _dump_log_objects(call: ServiceCall) -> None: + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + import objgraph # pylint: disable=import-outside-toplevel + obj_type = call.data[CONF_TYPE] _LOGGER.critical( @@ -220,6 +221,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + import cProfile # pylint: disable=import-outside-toplevel + start_time = int(time.time() * 1000000) persistent_notification.async_create( hass, @@ -246,6 +252,11 @@ async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall): async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + from guppy import hpy # pylint: disable=import-outside-toplevel + start_time = int(time.time() * 1000000) persistent_notification.async_create( hass, @@ -269,6 +280,11 @@ async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall) def _write_profile(profiler, cprofile_path, callgrind_path): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + from pyprof2calltree import convert # pylint: disable=import-outside-toplevel + profiler.create_stats() profiler.dump_stats(cprofile_path) convert(profiler.getstats(), callgrind_path) @@ -279,4 +295,9 @@ def _write_memory_profile(heap, heap_path): def _log_objects(*_): + # Imports deferred to avoid loading modules + # in memory since usually only one part of this + # integration is used at a time + import objgraph # pylint: disable=import-outside-toplevel + _LOGGER.critical("Memory Growth: %s", objgraph.growth(limit=100)) diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py index 89b2ccee171..0907602ed9d 100644 --- a/homeassistant/components/proliphix/climate.py +++ b/homeassistant/components/proliphix/climate.py @@ -6,14 +6,9 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -59,16 +54,13 @@ def setup_platform( class ProliphixThermostat(ClimateEntity): """Representation a Proliphix thermostat.""" + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__(self, pdp): """Initialize the thermostat.""" self._pdp = pdp self._name = None - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE - @property def should_poll(self): """Set up polling needed for thermostat.""" @@ -114,28 +106,28 @@ class ProliphixThermostat(ClimateEntity): return self._pdp.setback @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return the current state of the thermostat.""" state = self._pdp.hvac_state if state == 1: - return CURRENT_HVAC_OFF + return HVACAction.OFF if state in (3, 4, 5): - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if state in (6, 7): - return CURRENT_HVAC_COOL - return CURRENT_HVAC_IDLE + return HVACAction.COOLING + return HVACAction.IDLE @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return the current state of the thermostat.""" if self._pdp.is_heating: - return HVAC_MODE_HEAT + return HVACMode.HEAT if self._pdp.is_cooling: - return HVAC_MODE_COOL - return HVAC_MODE_OFF + return HVACMode.COOL + return HVACMode.OFF @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return available HVAC modes.""" return [] diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 7690ee5d1fc..949895cb76a 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -14,7 +14,7 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODES, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_ACTIONS, + HVACAction, ) from homeassistant.components.http import HomeAssistantView from homeassistant.components.humidifier.const import ( @@ -438,8 +438,8 @@ class PrometheusMetrics: "HVAC action", ["action"], ) - for action in CURRENT_HVAC_ACTIONS: - metric.labels(**dict(self._labels(state), action=action)).set( + for action in HVACAction: + metric.labels(**dict(self._labels(state), action=action.value)).set( float(action == current_action) ) diff --git a/homeassistant/components/prosegur/alarm_control_panel.py b/homeassistant/components/prosegur/alarm_control_panel.py index 1defc1624f1..e7176b241e5 100644 --- a/homeassistant/components/prosegur/alarm_control_panel.py +++ b/homeassistant/components/prosegur/alarm_control_panel.py @@ -5,10 +5,7 @@ from pyprosegur.auth import Auth from pyprosegur.installation import Installation, Status import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -43,6 +40,11 @@ async def async_setup_entry( class ProsegurAlarm(alarm.AlarmControlPanelEntity): """Representation of a Prosegur alarm status.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_HOME + ) + def __init__(self, contract: str, auth: Auth) -> None: """Initialize the Prosegur alarm panel.""" self._changed_by = None @@ -54,7 +56,6 @@ class ProsegurAlarm(alarm.AlarmControlPanelEntity): self._attr_code_arm_required = False self._attr_name = f"contract {self.contract}" self._attr_unique_id = self.contract - self._attr_supported_features = SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME async def async_update(self): """Update alarm status.""" diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index 5c0946f7ba6..bc33a1f7ffc 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -112,7 +112,7 @@ def _resize_image(image, opts): scale = new_width / float(old_width) new_height = int(float(old_height) * float(scale)) - img = img.resize((new_width, new_height), Image.ANTIALIAS) + img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) imgbuf = io.BytesIO() img.save(imgbuf, "JPEG", optimize=True, quality=quality) newimage = imgbuf.getvalue() diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index d1be59ebc87..ff3e610323f 100644 --- a/homeassistant/components/proxy/manifest.json +++ b/homeassistant/components/proxy/manifest.json @@ -2,6 +2,6 @@ "domain": "proxy", "name": "Camera Proxy", "documentation": "https://www.home-assistant.io/integrations/proxy", - "requirements": ["pillow==9.0.1"], + "requirements": ["pillow==9.1.0"], "codeowners": [] } diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 33de7f321dc..ef6cc3c364b 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -7,17 +7,15 @@ from pyps4_2ndscreen.errors import NotReady, PSDataIncomplete from pyps4_2ndscreen.media_art import TYPE_APP as PS_TYPE_APP import pyps4_2ndscreen.ps4 as pyps4 -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_TITLE, MEDIA_TYPE_APP, MEDIA_TYPE_GAME, - SUPPORT_PAUSE, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -46,13 +44,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -SUPPORT_PS4 = ( - SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_SELECT_SOURCE -) ICON = "mdi:sony-playstation" MEDIA_IMAGE_DEFAULT = None @@ -81,6 +72,14 @@ async def async_setup_entry( class PS4Device(MediaPlayerEntity): """Representation of a PS4.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, config, name, host, region, ps4, creds): """Initialize the ps4 device.""" self._entry_id = config.entry_id @@ -430,11 +429,6 @@ class PS4Device(MediaPlayerEntity): """Title of current playing media.""" return self._media_title - @property - def supported_features(self): - """Media player features that are supported.""" - return SUPPORT_PS4 - @property def source(self): """Return the current input source.""" diff --git a/homeassistant/components/ps4/translations/ca.json b/homeassistant/components/ps4/translations/ca.json index 53d45e5104d..6486c154360 100644 --- a/homeassistant/components/ps4/translations/ca.json +++ b/homeassistant/components/ps4/translations/ca.json @@ -25,6 +25,9 @@ "name": "Nom", "region": "Regi\u00f3" }, + "data_description": { + "code": "V\u00e9s a 'Configuraci\u00f3' de la teva consola PlayStation 4. A continuaci\u00f3, v\u00e9s a 'Configuraci\u00f3 de connexi\u00f3 de l'aplicaci\u00f3 m\u00f2bil' i selecciona 'Afegeix un dispositiu' per obtenir el PIN." + }, "description": "Introdueix la informaci\u00f3 de la teva PlayStation 4. Per al Codi PIN, ves a 'Configuraci\u00f3' a la consola de la PlayStation 4. Despr\u00e9s navega fins a 'Configuraci\u00f3 de la connexi\u00f3 de l'aplicaci\u00f3 m\u00f2bil' i selecciona 'Afegir dispositiu'. Introdueix el Codi PIN que es mostra. Consulta la [documentaci\u00f3](https://www.home-assistant.io/components/ps4/) per a m\u00e9s informaci\u00f3.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "Adre\u00e7a IP (deixa-ho en blanc si fas servir la detecci\u00f3 autom\u00e0tica).", "mode": "Mode de configuraci\u00f3" }, + "data_description": { + "ip_address": "Deixa-ho en blanc si selecciones el descobriment autom\u00e0tic." + }, "description": "Selecciona el mode de configuraci\u00f3. El camp de l'Adre\u00e7a IP es pot deixar en blanc si selecciones descobriment autom\u00e0tic (els dispositius es descobriran autom\u00e0ticament).", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/de.json b/homeassistant/components/ps4/translations/de.json index c177c5d81cc..22a50cdf3e3 100644 --- a/homeassistant/components/ps4/translations/de.json +++ b/homeassistant/components/ps4/translations/de.json @@ -25,6 +25,9 @@ "name": "Name", "region": "Region" }, + "data_description": { + "code": "Navigiere auf deiner PlayStation 4-Konsole zu \"Einstellungen\". Navigiere dann zu \"Mobile App-Verbindungseinstellungen\" und w\u00e4hle \"Ger\u00e4t hinzuf\u00fcgen\", um den Pin zu erhalten." + }, "description": "Gib deine PlayStation 4-Informationen ein. Navigiere f\u00fcr den PIN-Code auf der PlayStation 4-Konsole zu \"Einstellungen\". Navigiere dann zu \"Mobile App-Verbindungseinstellungen\" und w\u00e4hle \"Ger\u00e4t hinzuf\u00fcgen\" aus. Gib die angezeigte PIN-Code ein. Weitere Informationen findest du in der [Dokumentation](https://www.home-assistant.io/components/ps4/).", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP-Adresse (Leer lassen, wenn automatische Erkennung verwendet wird).", "mode": "Konfigurationsmodus" }, + "data_description": { + "ip_address": "Lasse das Feld leer, wenn du die automatische Erkennung ausw\u00e4hlst." + }, "description": "W\u00e4hle den Modus f\u00fcr die Konfiguration aus. Das Feld IP-Adresse kann leer bleiben, wenn die automatische Erkennung ausgew\u00e4hlt wird, da Ger\u00e4te automatisch erkannt werden.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/el.json b/homeassistant/components/ps4/translations/el.json index 5bc6b4a7b0e..f3899b682f3 100644 --- a/homeassistant/components/ps4/translations/el.json +++ b/homeassistant/components/ps4/translations/el.json @@ -25,6 +25,9 @@ "name": "\u038c\u03bd\u03bf\u03bc\u03b1", "region": "\u03a0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae" }, + "data_description": { + "code": "\u03a0\u03bb\u03bf\u03b7\u03b3\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \"\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2\" \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 PlayStation 4. \u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03bb\u03bf\u03b7\u03b3\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf 'Mobile App Connection Settings' \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 'Add Device' \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03bf pin." + }, "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03bf\u03c5 PlayStation 4. \u0393\u03b9\u03b1 \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \"\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2\" \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 PlayStation 4. \u03a3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c0\u03bb\u03bf\u03b7\u03b3\u03b7\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf 'Mobile App Connection Settings' (\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac) \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 'Add Device' (\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2). \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9. \u0391\u03bd\u03b1\u03c4\u03c1\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd [\u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7](https://www.home-assistant.io/components/ps4/) \u03b3\u03b9\u03b1 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP (\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5 \u03c4\u03b7\u03bd \u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u0391\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7).", "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2" }, + "data_description": { + "ip_address": "\u0391\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03b1\u03bd\u03b1\u03ba\u03ac\u03bb\u03c5\u03c8\u03b7." + }, "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2. \u03a4\u03bf \u03c0\u03b5\u03b4\u03af\u03bf \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03af\u03bd\u03b5\u03b9 \u03ba\u03b5\u03bd\u03cc \u03b5\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 Auto Discovery, \u03ba\u03b1\u03b8\u03ce\u03c2 \u03bf\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 \u03b8\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03c4\u03bf\u03cd\u03bd \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/en.json b/homeassistant/components/ps4/translations/en.json index 12b154f48de..1c8640efe2e 100644 --- a/homeassistant/components/ps4/translations/en.json +++ b/homeassistant/components/ps4/translations/en.json @@ -25,6 +25,9 @@ "name": "Name", "region": "Region" }, + "data_description": { + "code": "Navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device' to get the pin." + }, "description": "Enter your PlayStation 4 information. For PIN Code, navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device'. Enter the PIN Code that is displayed. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP Address (Leave empty if using Auto Discovery).", "mode": "Config Mode" }, + "data_description": { + "ip_address": "Leave blank if selecting auto-discovery." + }, "description": "Select mode for configuration. The IP Address field can be left blank if selecting Auto Discovery, as devices will be automatically discovered.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/et.json b/homeassistant/components/ps4/translations/et.json index ddc6f0b73a9..83ef7a6f14c 100644 --- a/homeassistant/components/ps4/translations/et.json +++ b/homeassistant/components/ps4/translations/et.json @@ -25,6 +25,9 @@ "name": "Nimi", "region": "Piirkond" }, + "data_description": { + "code": "Navigeeri oma PlayStation 4 konsoolis jaotisse \u201eSeaded\u201d. Seej\u00e4rel navigeeri jaotisse \"Mobiilirakenduse \u00fchenduse s\u00e4tted\" ja vali PIN-koodi saamiseks \"Lisa seade\"." + }, "description": "Sisesta oma PlayStation 4 teave. PIN koodi saamiseks mine PlayStation 4 konsooli \u201eSeaded\u201d. Seej\u00e4rel liigu jaotisse \u201eMobiilirakenduse \u00fchenduse seaded\u201d ja vali \u201eLisa seade\u201d. SisestaPIN kood . Lisateavet leiad [dokumentatsioonist] (https://www.home-assistant.io/components/ps4/).", "title": "" }, @@ -33,6 +36,9 @@ "ip_address": "IP-aadress (J\u00e4tke t\u00fchjaks, kui kasutate automaatset tuvastamist)", "mode": "Seadistuste re\u017eiim" }, + "data_description": { + "ip_address": "Automaatse avastamise valimiseks j\u00e4ta v\u00e4li t\u00fchjaks." + }, "description": "Valikonfigureerimise re\u017eiim. V\u00e4lja IP-aadress saab automaatse tuvastamise valimisel t\u00fchjaks j\u00e4tta, kuna seadmed avastatakse automaatselt.", "title": "" } diff --git a/homeassistant/components/ps4/translations/fr.json b/homeassistant/components/ps4/translations/fr.json index 8fc216fd20b..8bc2575d246 100644 --- a/homeassistant/components/ps4/translations/fr.json +++ b/homeassistant/components/ps4/translations/fr.json @@ -25,6 +25,9 @@ "name": "Nom", "region": "R\u00e9gion" }, + "data_description": { + "code": "Acc\u00e9dez aux \u00ab\u00a0Param\u00e8tres\u00a0\u00bb sur votre console PlayStation\u00a04, puis acc\u00e9dez \u00e0 \u00ab\u00a0Param\u00e8tres de connexion de l'application mobile\u00a0\u00bb et s\u00e9lectionnez \u00ab\u00a0Ajouter un appareil\u00a0\u00bb afin d'obtenir le code PIN." + }, "description": "Saisissez les informations de votre PlayStation\u00a04. Pour le Code PIN, acc\u00e9dez aux \u00ab\u00a0Param\u00e8tres\u00a0\u00bb sur votre console PlayStation\u00a04, puis acc\u00e9dez \u00e0 \u00ab\u00a0Param\u00e8tres de connexion de l'application mobile\u00a0\u00bb et s\u00e9lectionnez \u00ab\u00a0Ajouter un appareil\u00a0\u00bb. Entrez le Code PIN affich\u00e9. Consultez la [documentation](https://www.home-assistant.io/components/ps4/) pour plus d'informations.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "Adresse IP (laissez vide si vous utilisez la d\u00e9couverte automatique).", "mode": "Mode de configuration" }, + "data_description": { + "ip_address": "Laissez le champ vide si vous s\u00e9lectionnez la d\u00e9couverte automatique." + }, "description": "S\u00e9lectionnez le mode de configuration. Le champ Adresse IP peut \u00eatre laiss\u00e9 vide si vous s\u00e9lectionnez D\u00e9couverte automatique, car les appareils seront automatiquement d\u00e9couverts.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/hu.json b/homeassistant/components/ps4/translations/hu.json index 753ea60b282..24da7fc15f5 100644 --- a/homeassistant/components/ps4/translations/hu.json +++ b/homeassistant/components/ps4/translations/hu.json @@ -4,27 +4,30 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "credential_error": "Hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u0151 adatok beolvas\u00e1sa sor\u00e1n.", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", - "port_987_bind_error": "Nem siker\u00fclt a 987. porthoz kapcsol\u00f3dni. Tov\u00e1bbi inform\u00e1ci\u00f3 a [dokument\u00e1ci\u00f3ban] tal\u00e1lhat\u00f3 (https://www.home-assistant.io/components/ps4/).", - "port_997_bind_error": "Nem siker\u00fclt a 997-es porthoz kapcsol\u00f3dni. Tov\u00e1bbi inform\u00e1ci\u00f3 a [dokument\u00e1ci\u00f3ban] tal\u00e1lhat\u00f3 (https://www.home-assistant.io/components/ps4/)." + "port_987_bind_error": "Nem siker\u00fclt a 987. portot lefoglalni. Tov\u00e1bbi inform\u00e1ci\u00f3 a [dokument\u00e1ci\u00f3ban] tal\u00e1lhat\u00f3 (https://www.home-assistant.io/components/ps4/).", + "port_997_bind_error": "Nem siker\u00fclt a 997. portot lefoglalni. Tov\u00e1bbi inform\u00e1ci\u00f3 a [dokument\u00e1ci\u00f3ban] tal\u00e1lhat\u00f3 (https://www.home-assistant.io/components/ps4/)." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "credential_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s. Az \u00fajraind\u00edt\u00e1shoz nyomja meg a K\u00fcld\u00e9s gombot.", + "credential_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s. Az \u00fajraind\u00edt\u00e1shoz nyomja meg a Mehet gombot.", "login_failed": "Nem siker\u00fclt p\u00e1ros\u00edtani a PlayStation 4-gyel. Ellen\u0151rizze, hogy a PIN-k\u00f3d helyes-e.", "no_ipaddress": "\u00cdrja be a konfigur\u00e1lni k\u00edv\u00e1nt PlayStation 4 IP c\u00edm\u00e9t" }, "step": { "creds": { - "description": "Hiteles\u00edt\u0151 adatok sz\u00fcks\u00e9gesek. Nyomja meg a \u201eK\u00fcld\u00e9s\u201d gombot, majd a PS4 2. k\u00e9perny\u0151 alkalmaz\u00e1sban friss\u00edtse az eszk\u00f6z\u00f6ket, \u00e9s a folytat\u00e1shoz v\u00e1lassza a \u201eHome-Assistant\u201d eszk\u00f6zt.", + "description": "Hiteles\u00edt\u0151 adatok sz\u00fcks\u00e9gesek. Nyomja meg a \u201eMehet\u201d gombot, majd a PS4 2. k\u00e9perny\u0151 alkalmaz\u00e1sban friss\u00edtse az eszk\u00f6z\u00f6ket, \u00e9s a folytat\u00e1shoz v\u00e1lassza a \u201eHome-Assistant\u201d eszk\u00f6zt.", "title": "PlayStation 4" }, "link": { "data": { "code": "PIN-k\u00f3d", "ip_address": "IP c\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "region": "R\u00e9gi\u00f3" }, + "data_description": { + "code": "Keresse meg a PlayStation 4 konzol \"Be\u00e1ll\u00edt\u00e1sok\" gombj\u00e1t. Ezut\u00e1n keresse meg a \"Mobilalkalmaz\u00e1s-kapcsolat be\u00e1ll\u00edt\u00e1sai\" lehet\u0151s\u00e9get, \u00e9s v\u00e1lassza az \"Eszk\u00f6z hozz\u00e1ad\u00e1sa\" lehet\u0151s\u00e9get a pin lek\u00e9r\u00e9s\u00e9hez." + }, "description": "Adja meg a PlayStation 4 adatait. A PIN-k\u00f3d eset\u00e9ben keresse meg a PlayStation 4 konzol \u201eBe\u00e1ll\u00edt\u00e1sok\u201d elem\u00e9t. Ezut\u00e1n keresse meg a \u201eMobilalkalmaz\u00e1s-kapcsolat be\u00e1ll\u00edt\u00e1sai\u201d elemet, \u00e9s v\u00e1lassza az \u201eEszk\u00f6z hozz\u00e1ad\u00e1sa\u201d lehet\u0151s\u00e9get. \u00cdrja be a megjelen\u0151 PIN-k\u00f3d . Tov\u00e1bbi inform\u00e1ci\u00f3k a [dokument\u00e1ci\u00f3ban] tal\u00e1lhat\u00f3k (https://www.home-assistant.io/components/ps4/).", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP c\u00edm (Hagyja \u00fcresen az Automatikus Felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz).", "mode": "Konfigur\u00e1ci\u00f3s m\u00f3d" }, + "data_description": { + "ip_address": "Az automatikus felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz hagyja \u00fcresen" + }, "description": "V\u00e1lassza ki a m\u00f3dot a konfigur\u00e1l\u00e1shoz. Az IP c\u00edm mez\u0151 \u00fcresen maradhat, ha az Automatikus felder\u00edt\u00e9s lehet\u0151s\u00e9get v\u00e1lasztja, mivel az eszk\u00f6z\u00f6k automatikusan felfedez\u0151dnek.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/id.json b/homeassistant/components/ps4/translations/id.json index aab31564e16..beb15fb5c4c 100644 --- a/homeassistant/components/ps4/translations/id.json +++ b/homeassistant/components/ps4/translations/id.json @@ -25,6 +25,9 @@ "name": "Nama", "region": "Wilayah" }, + "data_description": { + "code": "Navigasikan ke 'Pengaturan' di konsol PlayStation 4 Anda. Kemudian navigasikan ke 'Pengaturan Koneksi Aplikasi Seluler' dan pilih 'Tambahkan Perangkat' untuk mendapatkan PIN." + }, "description": "Masukkan informasi PlayStation 4 Anda. Untuk Kode PIN, buka 'Pengaturan' di konsol PlayStation 4 Anda. Kemudian buka 'Pengaturan Koneksi Aplikasi Seluler' dan pilih 'Tambah Perangkat'. Masukkan Kode PIN yang ditampilkan. Lihat [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "Alamat IP (Kosongkan jika menggunakan Penemuan Otomatis).", "mode": "Mode Konfigurasi" }, + "data_description": { + "ip_address": "Kosongkan jika memilih penemuan otomatis." + }, "description": "Pilih mode untuk konfigurasi. Alamat IP dapat dikosongkan jika memilih Penemuan Otomatis karena perangkat akan ditemukan secara otomatis.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/it.json b/homeassistant/components/ps4/translations/it.json index 8df24f24afb..3faca963317 100644 --- a/homeassistant/components/ps4/translations/it.json +++ b/homeassistant/components/ps4/translations/it.json @@ -25,6 +25,9 @@ "name": "Nome", "region": "Area geografica" }, + "data_description": { + "code": "Vai a 'Impostazioni' sulla tua console PlayStation 4. Quindi vai su 'Impostazioni connessione app mobili' e seleziona 'Aggiungi dispositivo' per ottenere il pin." + }, "description": "Inserisci le informazioni per la tua PlayStation 4. Per il codice PIN, vai a \"Impostazioni\" sulla PlayStation 4. Quindi vai a 'Impostazioni di connessione Mobile App' e seleziona 'Aggiungi dispositivo'. Immettere il codice PIN visualizzato. Fai riferimento alla [documentazione](https://www.home-assistant.io/components/ps4/) per ulteriori informazioni.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "Indirizzo IP (Lascia vuoto se stai usando il rilevamento automatico).", "mode": "Modalit\u00e0 di configurazione" }, + "data_description": { + "ip_address": "Lascia vuoto se selezioni il rilevamento automatico." + }, "description": "Seleziona la modalit\u00e0 per la configurazione. Il campo per l'indirizzo IP pu\u00f2 essere lasciato vuoto se si seleziona il rilevamento automatico, poich\u00e9 i dispositivi saranno rilevati automaticamente.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/ja.json b/homeassistant/components/ps4/translations/ja.json index 6b1a162b387..92be62f7229 100644 --- a/homeassistant/components/ps4/translations/ja.json +++ b/homeassistant/components/ps4/translations/ja.json @@ -25,6 +25,9 @@ "name": "\u540d\u524d", "region": "\u30ea\u30fc\u30b8\u30e7\u30f3" }, + "data_description": { + "code": "PlayStation 4\u672c\u4f53\u306e' \u8a2d\u5b9a' \u306b\u79fb\u52d5\u3057\u307e\u3059\u3002\u6b21\u306b\u3001' \u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u63a5\u7d9a\u8a2d\u5b9a' \u306b\u79fb\u52d5\u3057\u3001' \u30c7\u30d0\u30a4\u30b9\u306e\u8ffd\u52a0' \u3092\u9078\u629e\u3057\u3066\u3001\u30d4\u30f3\u3092\u53d6\u5f97\u3057\u307e\u3059\u3002" + }, "description": "PlayStation4\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u307e\u3059\u3002 PIN\u30b3\u30fc\u30c9(PIN CodePIN Code)\u306e\u5834\u5408\u306f\u3001PlayStation4\u672c\u4f53\u306e '\u8a2d\u5b9a' \u306b\u79fb\u52d5\u3057\u307e\u3059\u3002\u6b21\u306b\u3001'\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u63a5\u7d9a\u8a2d\u5b9a' \u306b\u79fb\u52d5\u3057\u3066\u3001'\u30c7\u30d0\u30a4\u30b9\u306e\u8ffd\u52a0' \u3092\u9078\u629e\u3057\u307e\u3059\u3002\u8868\u793a\u3055\u308c\u305f PIN\u30b3\u30fc\u30c9(PIN CodePIN Code) \u3092\u5165\u529b\u3057\u307e\u3059\u3002\u8a73\u7d30\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8](https://www.home-assistant.io/components/ps4/) \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "Play Station 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP\u30a2\u30c9\u30ec\u30b9(\u81ea\u52d5\u691c\u51fa\u3092\u4f7f\u7528\u3059\u308b\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u307e\u3059)", "mode": "\u30b3\u30f3\u30d5\u30a3\u30b0\u30e2\u30fc\u30c9" }, + "data_description": { + "ip_address": "\u81ea\u52d5\u691c\u51fa\u3092\u9078\u629e\u3059\u308b\u5834\u5408\u306f\u3001\u7a7a\u767d\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002" + }, "description": "\u30b3\u30f3\u30d5\u30a3\u30ae\u30e5\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30e2\u30fc\u30c9\u3092\u9078\u629e\u3057\u307e\u3059\u3002\u81ea\u52d5\u691c\u51fa\u3092\u9078\u629e\u3059\u308b\u3068\u3001IP\u30a2\u30c9\u30ec\u30b9\u306e\u6b04\u304c\u7a7a\u767d\u306e\u307e\u307e\u3067\u3082\u30c7\u30d0\u30a4\u30b9\u306f\u81ea\u52d5\u7684\u306b\u691c\u51fa\u3055\u308c\u307e\u3059\u3002", "title": "Play Station 4" } diff --git a/homeassistant/components/ps4/translations/nl.json b/homeassistant/components/ps4/translations/nl.json index db59c7ab30a..1a6ddd26f7f 100644 --- a/homeassistant/components/ps4/translations/nl.json +++ b/homeassistant/components/ps4/translations/nl.json @@ -25,6 +25,9 @@ "name": "Naam", "region": "Regio" }, + "data_description": { + "code": "Navigeer naar 'Instellingen' op je PlayStation 4-systeem. Navigeer vervolgens naar 'Instellingen mobiele app-verbinding' en selecteer 'Apparaat toevoegen' om de pin te krijgen." + }, "description": "Voer je PlayStation 4-informatie in. Voor PIN-code navigeer je naar 'Instellingen' op je PlayStation 4-console. Ga vervolgens naar 'Verbindingsinstellingen mobiele app' en selecteer 'Apparaat toevoegen'. Voer de PIN-code in die wordt weergegeven. Raadpleeg de [documentatie](https://www.home-assistant.io/components/ps4/) voor meer informatie.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP-adres (leeg laten indien Auto Discovery wordt gebruikt).", "mode": "Configuratiemodus" }, + "data_description": { + "ip_address": "Leeg laten indien u auto-discovery selecteert." + }, "description": "Selecteer modus voor configuratie. Het veld IP-adres kan leeg worden gelaten als Auto Discovery wordt geselecteerd, omdat apparaten dan automatisch worden ontdekt.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/no.json b/homeassistant/components/ps4/translations/no.json index 185b0e031c5..69332862a33 100644 --- a/homeassistant/components/ps4/translations/no.json +++ b/homeassistant/components/ps4/translations/no.json @@ -25,6 +25,9 @@ "name": "Navn", "region": "" }, + "data_description": { + "code": "Naviger til \"Innstillinger\" p\u00e5 PlayStation 4-konsollen. Naviger deretter til \"Mobile App Connection Settings\" og velg \"Add Device\" for \u00e5 hente pinnen." + }, "description": "Skriv inn PlayStation 4-informasjonen din. For PIN kode , naviger til 'Innstillinger' p\u00e5 PlayStation 4-konsollen. Naviger deretter til 'Innstillinger for tilkobling av mobilapp' og velg 'Legg til enhet'. Skriv inn PIN kode som vises. Se [dokumentasjon] (https://www.home-assistant.io/components/ps4/) for mer informasjon.", "title": "" }, @@ -33,6 +36,9 @@ "ip_address": "IP adresse (La v\u00e6re tom hvis du bruker automatisk oppdagelse)", "mode": "Konfigureringsmodus" }, + "data_description": { + "ip_address": "La st\u00e5 tomt hvis du velger automatisk oppdagelse." + }, "description": "Velg modus for konfigurasjon. Feltet IP adresse kan st\u00e5 tomt hvis du velger automatisk oppdagelse, da enheter automatisk blir oppdaget.", "title": "" } diff --git a/homeassistant/components/ps4/translations/pl.json b/homeassistant/components/ps4/translations/pl.json index 9840363630d..af4d36b9bf2 100644 --- a/homeassistant/components/ps4/translations/pl.json +++ b/homeassistant/components/ps4/translations/pl.json @@ -25,6 +25,9 @@ "name": "Nazwa", "region": "Region" }, + "data_description": { + "code": "Przejd\u017a do \u201eUstawienia\u201d na konsoli PlayStation 4. Nast\u0119pnie przejd\u017a do \u201eUstawienia po\u0142\u0105czenia aplikacji mobilnej\u201d i wybierz \u201eDodaj urz\u0105dzenie\u201d, aby uzyska\u0107 kod PIN." + }, "description": "Wprowad\u017a informacje o PlayStation 4. Aby uzyska\u0107 'PIN', przejd\u017a do 'Ustawienia' na konsoli PlayStation 4. Nast\u0119pnie przejd\u017a do 'Ustawienia po\u0142\u0105czenia aplikacji mobilnej' i wybierz 'Dodaj urz\u0105dzenie'. Wprowad\u017a wy\u015bwietlony kod PIN. Zapoznaj si\u0119 z [dokumentacj\u0105](https://www.home-assistant.io/components/ps4/), by pozna\u0107 szczeg\u00f3\u0142y.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "Adres IP (pozostaw puste, je\u015bli u\u017cywasz wykrywania)", "mode": "Tryb konfiguracji" }, + "data_description": { + "ip_address": "Pozostaw puste, je\u015bli wybierasz automatyczne wykrywanie." + }, "description": "Wybierz tryb konfiguracji. Pole adresu IP mo\u017cna pozostawi\u0107 puste, je\u015bli wybierzesz opcj\u0119 Auto Discovery, poniewa\u017c urz\u0105dzenia zostan\u0105 automatycznie wykryte.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/pt-BR.json b/homeassistant/components/ps4/translations/pt-BR.json index 7938cbb6241..12d54264f78 100644 --- a/homeassistant/components/ps4/translations/pt-BR.json +++ b/homeassistant/components/ps4/translations/pt-BR.json @@ -25,6 +25,9 @@ "name": "Nome", "region": "Regi\u00e3o" }, + "data_description": { + "code": "Navegue at\u00e9 'Configura\u00e7\u00f5es' em seu console PlayStation 4. Em seguida, navegue at\u00e9 'Configura\u00e7\u00f5es de conex\u00e3o do aplicativo m\u00f3vel' e selecione 'Adicionar dispositivo' para obter o PIN." + }, "description": "Digite suas informa\u00e7\u00f5es do PlayStation 4. Para C\u00f3digo PIN, navegue at\u00e9 'Configura\u00e7\u00f5es' no seu console PlayStation 4. Em seguida, navegue at\u00e9 \"Configura\u00e7\u00f5es de conex\u00e3o de aplicativos m\u00f3veis\" e selecione \"Adicionar dispositivo\". Digite o C\u00f3digo PIN exibido. Consulte a [documenta\u00e7\u00e3o] (https://www.home-assistant.io/components/ps4/) para informa\u00e7\u00f5es adicionais.", "title": "Playstation 4" }, @@ -33,6 +36,9 @@ "ip_address": "Endere\u00e7o IP (Deixe em branco se estiver usando a Detec\u00e7\u00e3o autom\u00e1tica).", "mode": "Modo de configura\u00e7\u00e3o" }, + "data_description": { + "ip_address": "Deixe em branco se selecionar a descoberta autom\u00e1tica." + }, "description": "Selecione o modo para configura\u00e7\u00e3o. O campo Endere\u00e7o IP pode ser deixado em branco se selecionar Detec\u00e7\u00e3o Autom\u00e1tica, pois os dispositivos ser\u00e3o descobertos automaticamente.", "title": "Playstation 4" } diff --git a/homeassistant/components/ps4/translations/ru.json b/homeassistant/components/ps4/translations/ru.json index ea42e260ec8..646346095b0 100644 --- a/homeassistant/components/ps4/translations/ru.json +++ b/homeassistant/components/ps4/translations/ru.json @@ -25,6 +25,9 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "region": "\u0420\u0435\u0433\u0438\u043e\u043d" }, + "data_description": { + "code": "\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f PIN-\u043a\u043e\u0434\u0430 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043f\u0443\u043d\u043a\u0442\u0443 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 PlayStation 4. \u0417\u0430\u0442\u0435\u043c \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f** \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 **\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e**." + }, "description": "\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f PIN-\u043a\u043e\u0434\u0430 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043f\u0443\u043d\u043a\u0442\u0443 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 PlayStation 4. \u0417\u0430\u0442\u0435\u043c \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f** \u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 **\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e**. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439](https://www.home-assistant.io/components/ps4/) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0440\u0435\u0436\u0438\u043c\u0430 \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f)", "mode": "\u0420\u0435\u0436\u0438\u043c" }, + "data_description": { + "ip_address": "\u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435" + }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u041f\u043e\u043b\u0435 'IP-\u0430\u0434\u0440\u0435\u0441' \u043c\u043e\u0436\u043d\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'Auto Discovery', \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/tr.json b/homeassistant/components/ps4/translations/tr.json index 8243e179a98..a2e8149db7d 100644 --- a/homeassistant/components/ps4/translations/tr.json +++ b/homeassistant/components/ps4/translations/tr.json @@ -25,6 +25,9 @@ "name": "Ad", "region": "B\u00f6lge" }, + "data_description": { + "code": "PlayStation 4 konsolunuzda 'Ayarlar'a gidin. Ard\u0131ndan, \"Mobil Uygulama Ba\u011flant\u0131 Ayarlar\u0131\"na gidin ve PIN'i almak i\u00e7in \"Cihaz Ekle\"yi se\u00e7in." + }, "description": "PlayStation 4 bilgilerinizi girin. PIN Kodu i\u00e7in PlayStation 4 konsolunuzda 'Ayarlar'a gidin. Ard\u0131ndan \"Mobil Uygulama Ba\u011flant\u0131 Ayarlar\u0131\"na gidin ve \"Cihaz Ekle\"yi se\u00e7in. PIN Kodu kodunu girin. Ek bilgi i\u00e7in [belgelere](https://www.home-assistant.io/components/ps4/) bak\u0131n.", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP Adresi (Otomatik Ke\u015fif kullan\u0131l\u0131yorsa bo\u015f b\u0131rak\u0131n).", "mode": "Yap\u0131land\u0131rma Modu" }, + "data_description": { + "ip_address": "Otomatik bulma se\u00e7iliyorsa bo\u015f b\u0131rak\u0131n." + }, "description": "Yap\u0131land\u0131rma i\u00e7in modu se\u00e7in. IP Adresi alan\u0131, Otomatik Ke\u015fif se\u00e7ildi\u011finde cihazlar otomatik olarak ke\u015ffedilece\u011finden bo\u015f b\u0131rak\u0131labilir.", "title": "PlayStation 4" } diff --git a/homeassistant/components/ps4/translations/zh-Hant.json b/homeassistant/components/ps4/translations/zh-Hant.json index e9c8dac2a26..ae7e86377d8 100644 --- a/homeassistant/components/ps4/translations/zh-Hant.json +++ b/homeassistant/components/ps4/translations/zh-Hant.json @@ -25,6 +25,9 @@ "name": "\u540d\u7a31", "region": "\u5340\u57df" }, + "data_description": { + "code": "\u5207\u63db\u81f3 PlayStation 4 \u4e3b\u6a5f\u7684\u300c\u8a2d\u5b9a\u300d\u5167\uff0c\u4e26\u65bc\u300c\u884c\u52d5\u7a0b\u5f0f\u9023\u7dda\u8a2d\u5b9a\uff08Mobile App Connection Settings\uff09\u300d\u4e2d\u9078\u64c7\u300c\u65b0\u589e\u88dd\u7f6e\u300d\u4ee5\u53d6\u5f97 PIN \u78bc\u3002" + }, "description": "\u8f38\u5165\u60a8\u7684 PlayStation 4 \u8cc7\u8a0a\uff0c\u300cPIN \u78bc\u300d\u65bc PlayStation 4 \u4e3b\u6a5f\u7684\u300c\u8a2d\u5b9a\u300d\u5167\uff0c\u4e26\u65bc\u300c\u884c\u52d5\u7a0b\u5f0f\u9023\u7dda\u8a2d\u5b9a\uff08Mobile App Connection Settings\uff09\u300d\u4e2d\u9078\u64c7\u300c\u65b0\u589e\u88dd\u7f6e\u300d\u3002\u8f38\u5165\u6240\u986f\u793a\u7684 PIN \u78bc\u3002\u8acb\u53c3\u8003 [documentation](https://www.home-assistant.io/components/ps4/) \u4ee5\u7372\u5f97\u66f4\u591a\u8cc7\u8a0a\u3002", "title": "PlayStation 4" }, @@ -33,6 +36,9 @@ "ip_address": "IP \u4f4d\u5740\uff08\u5982\u679c\u4f7f\u7528\u81ea\u52d5\u641c\u7d22\u65b9\u5f0f\uff0c\u8acb\u4fdd\u7559\u7a7a\u767d\uff09\u3002", "mode": "\u8a2d\u5b9a\u6a21\u5f0f" }, + "data_description": { + "ip_address": "\u5047\u5982\u9078\u64c7\u4f7f\u7528\u81ea\u52d5\u641c\u7d22\u8acb\u4fdd\u6301\u7a7a\u767d" + }, "description": "\u9078\u64c7\u6a21\u5f0f\u4ee5\u9032\u884c\u8a2d\u5b9a\u3002\u5047\u5982\u9078\u64c7\u81ea\u52d5\u641c\u7d22\u6a21\u5f0f\u7684\u8a71\uff0c\u7531\u65bc\u6703\u81ea\u52d5\u9032\u884c\u88dd\u7f6e\u641c\u5c0b\uff0cIP \u4f4d\u5740\u53ef\u4fdd\u7559\u70ba\u7a7a\u767d\u3002", "title": "PlayStation 4" } diff --git a/homeassistant/components/pure_energie/translations/cs.json b/homeassistant/components/pure_energie/translations/cs.json new file mode 100644 index 00000000000..76dabdfb963 --- /dev/null +++ b/homeassistant/components/pure_energie/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "flow_title": "{model} ({host})", + "step": { + "user": { + "data": { + "host": "Hostitel" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pure_energie/translations/fr.json b/homeassistant/components/pure_energie/translations/fr.json index 7ab74aae074..e121c7f655c 100644 --- a/homeassistant/components/pure_energie/translations/fr.json +++ b/homeassistant/components/pure_energie/translations/fr.json @@ -13,6 +13,10 @@ "data": { "host": "H\u00f4te" } + }, + "zeroconf_confirm": { + "description": "Voulez-vous ajouter Pure Energie Meter (`{model}`) \u00e0 Home Assistant\u00a0?", + "title": "D\u00e9couverte de l'appareil Pure Energie Meter" } } } diff --git a/homeassistant/components/pvoutput/manifest.json b/homeassistant/components/pvoutput/manifest.json index 021fffe0e01..947238df833 100644 --- a/homeassistant/components/pvoutput/manifest.json +++ b/homeassistant/components/pvoutput/manifest.json @@ -3,7 +3,7 @@ "name": "PVOutput", "documentation": "https://www.home-assistant.io/integrations/pvoutput", "config_flow": true, - "codeowners": ["@fabaff", "@frenck"], + "codeowners": ["@frenck"], "requirements": ["pvo==0.2.2"], "iot_class": "cloud_polling", "quality_scale": "platinum" diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ja.json b/homeassistant/components/pvpc_hourly_pricing/translations/ja.json index 919186d0e62..d88ee379678 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ja.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ja.json @@ -9,7 +9,7 @@ "name": "\u30bb\u30f3\u30b5\u30fc\u540d", "power": "\u5951\u7d04\u96fb\u529b (kW)", "power_p3": "\u8c37\u9593(valley period) P3 (kW)\u306e\u5951\u7d04\u96fb\u529b", - "tariff": "\u5730\u57df\u5225\u9069\u7528\u95a2\u7a0e" + "tariff": "\u5730\u57df\u5225\u9069\u7528\u95a2\u7a0e(Applicable tariff)" }, "description": "\u3053\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u516c\u5f0fAPI\u3092\u4f7f\u7528\u3057\u3066\u3001\u30b9\u30da\u30a4\u30f3\u3067\u306e[\u96fb\u6c17\u306e\u6642\u9593\u4fa1\u683c((hourly pricing of electricity)PVPC)](https://www.esios.ree.es/es/pvpc) \u3092\u53d6\u5f97\u3057\u307e\u3059\u3002\n\u3088\u308a\u6b63\u78ba\u306a\u8aac\u660e\u306b\u3064\u3044\u3066\u306f\u3001[\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3 \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/) \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "\u30bb\u30f3\u30b5\u30fc\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" @@ -22,7 +22,7 @@ "data": { "power": "\u5951\u7d04\u96fb\u529b (kW)", "power_p3": "\u8c37\u9593(valley period) P3 (kW)\u306e\u5951\u7d04\u96fb\u529b", - "tariff": "\u5730\u57df\u5225\u9069\u7528\u95a2\u7a0e" + "tariff": "\u5730\u57df\u5225\u9069\u7528\u95a2\u7a0e(Applicable tariff)" }, "description": "\u3053\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u516c\u5f0fAPI\u3092\u4f7f\u7528\u3057\u3066\u3001\u30b9\u30da\u30a4\u30f3\u3067\u306e[\u96fb\u6c17\u306e\u6642\u9593\u4fa1\u683c(hourly pricing of electricity)PVPC)](https://www.esios.ree.es/es/pvpc) \u3092\u53d6\u5f97\u3057\u307e\u3059\u3002\n\u3088\u308a\u6b63\u78ba\u306a\u8aac\u660e\u306b\u3064\u3044\u3066\u306f\u3001[\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3 \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/) \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\u3002", "title": "\u30bb\u30f3\u30b5\u30fc\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py index d3bc2e8d31e..1dd6e3d3799 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -236,7 +236,6 @@ class StubPrinter: def _call_print(self, *objects, **kwargs): """Print text.""" - # pylint: disable=no-self-use _LOGGER.warning("Don't use print() inside scripts. Use logger.info() instead") @@ -246,7 +245,6 @@ class TimeWrapper: # Class variable, only going to warn once per Home Assistant run warned = False - # pylint: disable=no-self-use def sleep(self, *args, **kwargs): """Sleep method that warns once.""" if not TimeWrapper.warned: diff --git a/homeassistant/components/qnap_qsw/__init__.py b/homeassistant/components/qnap_qsw/__init__.py new file mode 100644 index 00000000000..c1b96a3298e --- /dev/null +++ b/homeassistant/components/qnap_qsw/__init__.py @@ -0,0 +1,89 @@ +"""The QNAP QSW integration.""" +from __future__ import annotations + +from typing import Any + +from aioqsw.const import ( + QSD_FIRMWARE, + QSD_FIRMWARE_INFO, + QSD_MAC, + QSD_PRODUCT, + QSD_SYSTEM_BOARD, +) +from aioqsw.localapi import ConnectionOptions, QnapQswApi + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, MANUFACTURER +from .coordinator import QswUpdateCoordinator + +PLATFORMS: list[Platform] = [Platform.SENSOR] + + +class QswEntity(CoordinatorEntity[QswUpdateCoordinator]): + """Define an QNAP QSW entity.""" + + def __init__( + self, + coordinator: QswUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + self._attr_device_info = DeviceInfo( + configuration_url=entry.data[CONF_URL], + connections={ + ( + CONNECTION_NETWORK_MAC, + self.get_device_value(QSD_SYSTEM_BOARD, QSD_MAC), + ) + }, + manufacturer=MANUFACTURER, + model=self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT), + name=self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT), + sw_version=self.get_device_value(QSD_FIRMWARE_INFO, QSD_FIRMWARE), + ) + + def get_device_value(self, key: str, subkey: str) -> Any: + """Return device value by key.""" + value = None + if key in self.coordinator.data: + data = self.coordinator.data[key] + if subkey in data: + value = data[subkey] + return value + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up QNAP QSW from a config entry.""" + options = ConnectionOptions( + entry.data[CONF_URL], + entry.data[CONF_USERNAME], + entry.data[CONF_PASSWORD], + ) + + qsw = QnapQswApi(aiohttp_client.async_get_clientsession(hass), options) + + coordinator = QswUpdateCoordinator(hass, qsw) + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/qnap_qsw/config_flow.py b/homeassistant/components/qnap_qsw/config_flow.py new file mode 100644 index 00000000000..891c72c9911 --- /dev/null +++ b/homeassistant/components/qnap_qsw/config_flow.py @@ -0,0 +1,65 @@ +"""Config flow for QNAP QSW.""" +from __future__ import annotations + +from typing import Any + +from aioqsw.exceptions import LoginError, QswError +from aioqsw.localapi import ConnectionOptions, QnapQswApi +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME +from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.device_registry import format_mac + +from .const import DOMAIN + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle config flow for a QNAP QSW device.""" + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + errors = {} + + if user_input is not None: + url = user_input[CONF_URL] + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] + + qsw = QnapQswApi( + aiohttp_client.async_get_clientsession(self.hass), + ConnectionOptions(url, username, password), + ) + + try: + system_board = await qsw.validate() + except LoginError: + errors[CONF_PASSWORD] = "invalid_auth" + except QswError: + errors[CONF_URL] = "cannot_connect" + else: + mac = system_board.get_mac() + if mac is None: + raise AbortFlow("invalid_id") + + await self.async_set_unique_id(format_mac(mac)) + self._abort_if_unique_id_configured() + + title = f"QNAP {system_board.get_product()} {mac}" + return self.async_create_entry(title=title, data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_URL): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/qnap_qsw/const.py b/homeassistant/components/qnap_qsw/const.py new file mode 100644 index 00000000000..b55a817927f --- /dev/null +++ b/homeassistant/components/qnap_qsw/const.py @@ -0,0 +1,12 @@ +"""Constants for the QNAP QSW integration.""" + +from typing import Final + +ATTR_MAX: Final = "max" + +DOMAIN: Final = "qnap_qsw" +MANUFACTURER: Final = "QNAP" + +RPM: Final = "rpm" + +QSW_TIMEOUT_SEC: Final = 25 diff --git a/homeassistant/components/qnap_qsw/coordinator.py b/homeassistant/components/qnap_qsw/coordinator.py new file mode 100644 index 00000000000..064953b1446 --- /dev/null +++ b/homeassistant/components/qnap_qsw/coordinator.py @@ -0,0 +1,42 @@ +"""The QNAP QSW coordinator.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +from aioqsw.exceptions import QswError +from aioqsw.localapi import QnapQswApi +import async_timeout + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, QSW_TIMEOUT_SEC + +SCAN_INTERVAL = timedelta(seconds=60) + +_LOGGER = logging.getLogger(__name__) + + +class QswUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the QNAP QSW device.""" + + def __init__(self, hass: HomeAssistant, qsw: QnapQswApi) -> None: + """Initialize.""" + self.qsw = qsw + + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + ) + + async def _async_update_data(self): + """Update data via library.""" + async with async_timeout.timeout(QSW_TIMEOUT_SEC): + try: + await self.qsw.update() + except QswError as error: + raise UpdateFailed(error) from error + return self.qsw.data() diff --git a/homeassistant/components/qnap_qsw/manifest.json b/homeassistant/components/qnap_qsw/manifest.json new file mode 100644 index 00000000000..17709b275ca --- /dev/null +++ b/homeassistant/components/qnap_qsw/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "qnap_qsw", + "name": "QNAP QSW", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/qnap_qsw", + "requirements": ["aioqsw==0.0.5"], + "codeowners": ["@Noltari"], + "iot_class": "local_polling", + "loggers": ["aioqsw"] +} diff --git a/homeassistant/components/qnap_qsw/sensor.py b/homeassistant/components/qnap_qsw/sensor.py new file mode 100644 index 00000000000..8453232ce6f --- /dev/null +++ b/homeassistant/components/qnap_qsw/sensor.py @@ -0,0 +1,138 @@ +"""Support for the QNAP QSW sensors.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Final + +from aioqsw.const import ( + QSD_FAN1_SPEED, + QSD_FAN2_SPEED, + QSD_PRODUCT, + QSD_SYSTEM_BOARD, + QSD_SYSTEM_SENSOR, + QSD_SYSTEM_TIME, + QSD_TEMP, + QSD_TEMP_MAX, + QSD_UPTIME, +) + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import TEMP_CELSIUS, TIME_SECONDS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import QswEntity +from .const import ATTR_MAX, DOMAIN, RPM +from .coordinator import QswUpdateCoordinator + + +@dataclass +class QswSensorEntityDescription(SensorEntityDescription): + """A class that describes QNAP QSW sensor entities.""" + + attributes: dict[str, list[str]] | None = None + subkey: str = "" + + +SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( + QswSensorEntityDescription( + icon="mdi:fan-speed-1", + key=QSD_SYSTEM_SENSOR, + name="Fan 1 Speed", + native_unit_of_measurement=RPM, + state_class=SensorStateClass.MEASUREMENT, + subkey=QSD_FAN1_SPEED, + ), + QswSensorEntityDescription( + icon="mdi:fan-speed-2", + key=QSD_SYSTEM_SENSOR, + name="Fan 2 Speed", + native_unit_of_measurement=RPM, + state_class=SensorStateClass.MEASUREMENT, + subkey=QSD_FAN2_SPEED, + ), + QswSensorEntityDescription( + attributes={ + ATTR_MAX: [QSD_SYSTEM_SENSOR, QSD_TEMP_MAX], + }, + device_class=SensorDeviceClass.TEMPERATURE, + key=QSD_SYSTEM_SENSOR, + name="Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + subkey=QSD_TEMP, + ), + QswSensorEntityDescription( + icon="mdi:timer-outline", + key=QSD_SYSTEM_TIME, + entity_category=EntityCategory.DIAGNOSTIC, + name="Uptime", + native_unit_of_measurement=TIME_SECONDS, + state_class=SensorStateClass.TOTAL_INCREASING, + subkey=QSD_UPTIME, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Add QNAP QSW sensors from a config_entry.""" + coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + QswSensor(coordinator, description, entry) + for description in SENSOR_TYPES + if ( + description.key in coordinator.data + and description.subkey in coordinator.data[description.key] + ) + ) + + +class QswSensor(QswEntity, SensorEntity): + """Define a QNAP QSW sensor.""" + + entity_description: QswSensorEntityDescription + + def __init__( + self, + coordinator: QswUpdateCoordinator, + description: QswSensorEntityDescription, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry) + self._attr_name = ( + f"{self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT)} {description.name}" + ) + self._attr_unique_id = ( + f"{entry.unique_id}_{description.key}_{description.subkey}" + ) + self.entity_description = description + self._async_update_attrs() + + @callback + def _handle_coordinator_update(self) -> None: + """Update attributes when the coordinator updates.""" + self._async_update_attrs() + super()._handle_coordinator_update() + + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + self._attr_native_value = self.get_device_value( + self.entity_description.key, self.entity_description.subkey + ) + + if self.entity_description.attributes: + self._attr_extra_state_attributes = { + key: self.get_device_value(val[0], val[1]) + for key, val in self.entity_description.attributes.items() + } diff --git a/homeassistant/components/qnap_qsw/strings.json b/homeassistant/components/qnap_qsw/strings.json new file mode 100644 index 00000000000..351245a9591 --- /dev/null +++ b/homeassistant/components/qnap_qsw/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "invalid_id": "Device returned an invalid unique ID" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + "step": { + "user": { + "data": { + "url": "[%key:common::config_flow::data::url%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + } + } +} diff --git a/homeassistant/components/qnap_qsw/translations/de.json b/homeassistant/components/qnap_qsw/translations/de.json new file mode 100644 index 00000000000..ad2599e24c4 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "invalid_id": "Ger\u00e4t hat eine ung\u00fcltige eindeutige ID zur\u00fcckgegeben" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "url": "URL", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/el.json b/homeassistant/components/qnap_qsw/translations/el.json new file mode 100644 index 00000000000..1bea5dcc9d7 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/el.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "invalid_id": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b5\u03c0\u03ad\u03c3\u03c4\u03c1\u03b5\u03c8\u03b5 \u03ad\u03bd\u03b1 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03bc\u03bf\u03bd\u03b1\u03b4\u03b9\u03ba\u03cc \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/en.json b/homeassistant/components/qnap_qsw/translations/en.json new file mode 100644 index 00000000000..b6f68f2f062 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "invalid_id": "Device returned an invalid unique ID" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "user": { + "data": { + "password": "Password", + "url": "URL", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/et.json b/homeassistant/components/qnap_qsw/translations/et.json new file mode 100644 index 00000000000..eb7ddb6dbe7 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "invalid_id": "Seade tagastas kehtetu kordumatu ID" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "url": "URL", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/fr.json b/homeassistant/components/qnap_qsw/translations/fr.json new file mode 100644 index 00000000000..2631bcdfc87 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "invalid_id": "L'appareil a renvoy\u00e9 un ID unique non valide" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "url": "URL", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/hu.json b/homeassistant/components/qnap_qsw/translations/hu.json new file mode 100644 index 00000000000..c41ba084d95 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "invalid_id": "A k\u00e9sz\u00fcl\u00e9k \u00e9rv\u00e9nytelen egyedi azonos\u00edt\u00f3t k\u00fcld\u00f6tt vissza" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "url": "URL", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/id.json b/homeassistant/components/qnap_qsw/translations/id.json new file mode 100644 index 00000000000..b34bcd046d9 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "invalid_id": "Perangkat mengembalikan ID unik yang tidak valid" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "url": "URL", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/it.json b/homeassistant/components/qnap_qsw/translations/it.json new file mode 100644 index 00000000000..0759ef4ca8a --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "invalid_id": "Il dispositivo ha restituito un ID univoco non valido" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "password": "Password", + "url": "URL", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/nl.json b/homeassistant/components/qnap_qsw/translations/nl.json new file mode 100644 index 00000000000..10ec94f589d --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "invalid_id": "Het apparaat heeft een ongeldige unieke ID teruggestuurd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "url": "URL", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/no.json b/homeassistant/components/qnap_qsw/translations/no.json new file mode 100644 index 00000000000..9eff9c22080 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "invalid_id": "Enheten returnerte en ugyldig unik ID" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "url": "URL", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/pl.json b/homeassistant/components/qnap_qsw/translations/pl.json new file mode 100644 index 00000000000..e90d527a7a7 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "invalid_id": "Urz\u0105dzenie zwr\u00f3ci\u0142o nieprawid\u0142owy unikalny identyfikator" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "url": "URL", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/pt-BR.json b/homeassistant/components/qnap_qsw/translations/pt-BR.json new file mode 100644 index 00000000000..b9829d78944 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 est\u00e1 configurado", + "invalid_id": "O dispositivo retornou um ID exclusivo inv\u00e1lido" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "url": "URL", + "username": "Usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/ru.json b/homeassistant/components/qnap_qsw/translations/ru.json new file mode 100644 index 00000000000..ae3869552d0 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/ru.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "invalid_id": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0432\u0435\u0440\u043d\u0443\u043b\u043e \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 ID." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "url": "URL-\u0430\u0434\u0440\u0435\u0441", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qnap_qsw/translations/zh-Hant.json b/homeassistant/components/qnap_qsw/translations/zh-Hant.json new file mode 100644 index 00000000000..f29c42d6eb6 --- /dev/null +++ b/homeassistant/components/qnap_qsw/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "invalid_id": "\u88dd\u7f6e\u56de\u8986\u4e86\u7121\u6548\u7684\u552f\u4e00 ID" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "url": "\u7db2\u5740", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 259b3ec3b7b..66a50bf98d9 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,7 +2,7 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==9.0.1", "pyzbar==0.1.7"], + "requirements": ["pillow==9.1.0", "pyzbar==0.1.7"], "codeowners": [], "iot_class": "calculated", "loggers": ["pyzbar"] diff --git a/homeassistant/components/qwikswitch/light.py b/homeassistant/components/qwikswitch/light.py index 9c31c4660d3..2109b8d2c3b 100644 --- a/homeassistant/components/qwikswitch/light.py +++ b/homeassistant/components/qwikswitch/light.py @@ -1,7 +1,7 @@ """Support for Qwikswitch Relays and Dimmers.""" from __future__ import annotations -from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -33,6 +33,11 @@ class QSLight(QSToggleEntity, LightEntity): return self.device.value if self.device.is_dimmer else None @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS if self.device.is_dimmer else 0 + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + return ColorMode.BRIGHTNESS if self.device.is_dimmer else ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[ColorMode]: + """Flag supported color modes.""" + return {self.color_mode} diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index d190a2e96ea..c0c10c5b1b3 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -65,12 +65,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( native_unit_of_measurement="Movies", icon="mdi:television", ), - SensorEntityDescription( - key="wanted", - name="Wanted", - native_unit_of_measurement="Movies", - icon="mdi:television", - ), SensorEntityDescription( key="movies", name="Movies", @@ -139,15 +133,10 @@ def setup_platform( """Set up the Radarr platform.""" conditions = config[CONF_MONITORED_CONDITIONS] # deprecated in 2022.3 - if "wanted" in conditions: - _LOGGER.warning( - "Wanted is not a valid condition option. Please remove it from your config" - ) entities = [ RadarrSensor(hass, config, description) for description in SENSOR_TYPES if description.key in conditions - if description.key != "wanted" ] add_entities(entities, True) diff --git a/homeassistant/components/radio_browser/translations/cs.json b/homeassistant/components/radio_browser/translations/cs.json new file mode 100644 index 00000000000..19f5d1e1587 --- /dev/null +++ b/homeassistant/components/radio_browser/translations/cs.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/fr.json b/homeassistant/components/radio_browser/translations/fr.json index 807ba246694..de1e4f25261 100644 --- a/homeassistant/components/radio_browser/translations/fr.json +++ b/homeassistant/components/radio_browser/translations/fr.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "user": { + "description": "Voulez-vous ajouter le navigateur radio \u00e0 Home Assistant\u00a0?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/radio_browser/translations/he.json b/homeassistant/components/radio_browser/translations/he.json index d0c3523da94..89c5e30a440 100644 --- a/homeassistant/components/radio_browser/translations/he.json +++ b/homeassistant/components/radio_browser/translations/he.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." + }, + "step": { + "user": { + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05d3\u05e4\u05d3\u05e4\u05df \u05e8\u05d3\u05d9\u05d5 \u05d0\u05dc Home Assistant?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index 6a4f34e6d3a..dbf013ffa9a 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -9,26 +9,19 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, + FAN_AUTO, FAN_OFF, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_HOME, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_HOST, PRECISION_HALVES, - STATE_ON, TEMP_FAHRENHEIT, ) from homeassistant.core import HomeAssistant @@ -51,9 +44,9 @@ STATE_CIRCULATE = "circulate" PRESET_MODES = [PRESET_HOME, PRESET_ALTERNATE, PRESET_AWAY, PRESET_HOLIDAY] -OPERATION_LIST = [HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF] -CT30_FAN_OPERATION_LIST = [STATE_ON, HVAC_MODE_AUTO] -CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, HVAC_MODE_AUTO] +OPERATION_LIST = [HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF] +CT30_FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO] +CT80_FAN_OPERATION_LIST = [FAN_ON, STATE_CIRCULATE, FAN_AUTO] # Mappings from radiotherm json data codes to and from Home Assistant state # flags. CODE is the thermostat integer code and these map to and @@ -61,21 +54,21 @@ CT80_FAN_OPERATION_LIST = [STATE_ON, STATE_CIRCULATE, HVAC_MODE_AUTO] # Programmed temperature mode of the thermostat. CODE_TO_TEMP_MODE = { - 0: HVAC_MODE_OFF, - 1: HVAC_MODE_HEAT, - 2: HVAC_MODE_COOL, - 3: HVAC_MODE_AUTO, + 0: HVACMode.OFF, + 1: HVACMode.HEAT, + 2: HVACMode.COOL, + 3: HVACMode.AUTO, } TEMP_MODE_TO_CODE = {v: k for k, v in CODE_TO_TEMP_MODE.items()} # Programmed fan mode (circulate is supported by CT80 models) -CODE_TO_FAN_MODE = {0: HVAC_MODE_AUTO, 1: STATE_CIRCULATE, 2: STATE_ON} +CODE_TO_FAN_MODE = {0: FAN_AUTO, 1: STATE_CIRCULATE, 2: FAN_ON} FAN_MODE_TO_CODE = {v: k for k, v in CODE_TO_FAN_MODE.items()} # Active thermostat state (is it heating or cooling?). In the future # this should probably made into heat and cool binary sensors. -CODE_TO_TEMP_STATE = {0: CURRENT_HVAC_IDLE, 1: CURRENT_HVAC_HEAT, 2: CURRENT_HVAC_COOL} +CODE_TO_TEMP_STATE = {0: HVACAction.IDLE, 1: HVACAction.HEATING, 2: HVACAction.COOLING} # Active fan state. This is if the fan is actually on or not. In the # future this should probably made into a binary sensor for the fan. @@ -105,9 +98,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_PRESET_MODE - - def setup_platform( hass: HomeAssistant, config: ConfigType, @@ -141,18 +131,25 @@ def setup_platform( class RadioThermostat(ClimateEntity): """Representation of a Radio Thermostat.""" + _attr_hvac_modes = OPERATION_LIST + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, device, hold_temp): """Initialize the thermostat.""" self.device = device self._target_temperature = None self._current_temperature = None self._current_humidity = None - self._current_operation = HVAC_MODE_OFF + self._current_operation = HVACMode.OFF self._name = None self._fmode = None self._fstate = None self._tmode = None - self._tstate = None + self._tstate: HVACAction | None = None self._hold_temp = hold_temp self._hold_set = False self._prev_temp = None @@ -163,11 +160,6 @@ class RadioThermostat(ClimateEntity): # Fan circulate mode is only supported by the CT80 models. self._is_model_ct80 = isinstance(self.device, radiotherm.thermostat.CT80) - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - async def async_added_to_hass(self): """Register callbacks.""" # Set the time on the device. This shouldn't be in the @@ -225,19 +217,14 @@ class RadioThermostat(ClimateEntity): return self._current_humidity @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return the current operation. head, cool idle.""" return self._current_operation @property - def hvac_modes(self): - """Return the operation modes list.""" - return OPERATION_LIST - - @property - def hvac_action(self): + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: return None return self._tstate @@ -314,20 +301,20 @@ class RadioThermostat(ClimateEntity): self._hold_set = CODE_TO_HOLD_STATE[data["hold"]] self._current_operation = self._tmode - if self._tmode == HVAC_MODE_COOL: + if self._tmode == HVACMode.COOL: self._target_temperature = data["t_cool"] - elif self._tmode == HVAC_MODE_HEAT: + elif self._tmode == HVACMode.HEAT: self._target_temperature = data["t_heat"] - elif self._tmode == HVAC_MODE_AUTO: + elif self._tmode == HVACMode.AUTO: # This doesn't really work - tstate is only set if the HVAC is # active. If it's idle, we don't know what to do with the target # temperature. - if self._tstate == CURRENT_HVAC_COOL: + if self._tstate == HVACAction.COOLING: self._target_temperature = data["t_cool"] - elif self._tstate == CURRENT_HVAC_HEAT: + elif self._tstate == HVACAction.HEATING: self._target_temperature = data["t_heat"] else: - self._current_operation = HVAC_MODE_OFF + self._current_operation = HVACMode.OFF def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -336,14 +323,14 @@ class RadioThermostat(ClimateEntity): temperature = round_temp(temperature) - if self._current_operation == HVAC_MODE_COOL: + if self._current_operation == HVACMode.COOL: self.device.t_cool = temperature - elif self._current_operation == HVAC_MODE_HEAT: + elif self._current_operation == HVACMode.HEAT: self.device.t_heat = temperature - elif self._current_operation == HVAC_MODE_AUTO: - if self._tstate == CURRENT_HVAC_COOL: + elif self._current_operation == HVACMode.AUTO: + if self._tstate == HVACAction.COOLING: self.device.t_cool = temperature - elif self._tstate == CURRENT_HVAC_HEAT: + elif self._tstate == HVACAction.HEATING: self.device.t_heat = temperature # Only change the hold if requested or if hold mode was turned @@ -366,15 +353,15 @@ class RadioThermostat(ClimateEntity): "minute": now.minute, } - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set operation mode (auto, cool, heat, off).""" - if hvac_mode in (HVAC_MODE_OFF, HVAC_MODE_AUTO): + if hvac_mode in (HVACMode.OFF, HVACMode.AUTO): self.device.tmode = TEMP_MODE_TO_CODE[hvac_mode] # Setting t_cool or t_heat automatically changes tmode. - elif hvac_mode == HVAC_MODE_COOL: + elif hvac_mode == HVACMode.COOL: self.device.t_cool = self._target_temperature - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: self.device.t_heat = self._target_temperature def set_preset_mode(self, preset_mode): diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py index 1c850915e26..fb404adb199 100644 --- a/homeassistant/components/rainmachine/binary_sensor.py +++ b/homeassistant/components/rainmachine/binary_sensor.py @@ -20,7 +20,7 @@ from .const import ( DATA_RESTRICTIONS_UNIVERSAL, DOMAIN, ) -from .model import RainMachineSensorDescriptionMixin +from .model import RainMachineDescriptionMixinApiCategory TYPE_FLOW_SENSOR = "flow_sensor" TYPE_FREEZE = "freeze" @@ -35,7 +35,7 @@ TYPE_WEEKDAY = "weekday" @dataclass class RainMachineBinarySensorDescription( - BinarySensorEntityDescription, RainMachineSensorDescriptionMixin + BinarySensorEntityDescription, RainMachineDescriptionMixinApiCategory ): """Describe a RainMachine binary sensor.""" @@ -119,7 +119,7 @@ async def async_setup_entry( coordinators = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] @callback - def async_get_sensor(api_category: str) -> partial: + def async_get_sensor_by_api_category(api_category: str) -> partial: """Generate the appropriate sensor object for an API category.""" if api_category == DATA_PROVISION_SETTINGS: return partial( @@ -143,7 +143,9 @@ async def async_setup_entry( async_add_entities( [ - async_get_sensor(description.api_category)(controller, description) + async_get_sensor_by_api_category(description.api_category)( + controller, description + ) for description in BINARY_SENSOR_DESCRIPTIONS ] ) diff --git a/homeassistant/components/rainmachine/const.py b/homeassistant/components/rainmachine/const.py index 56c1660a0ba..f386341c161 100644 --- a/homeassistant/components/rainmachine/const.py +++ b/homeassistant/components/rainmachine/const.py @@ -1,6 +1,8 @@ """Define constants for the SimpliSafe component.""" import logging +from homeassistant.backports.enum import StrEnum + LOGGER = logging.getLogger(__package__) DOMAIN = "rainmachine" @@ -17,3 +19,18 @@ DATA_ZONES = "zones" DEFAULT_PORT = 8080 DEFAULT_ZONE_RUN = 60 * 10 + + +class RunStates(StrEnum): + """Define an enum for program/zone run states.""" + + NOT_RUNNING = "Not Running" + QUEUED = "Queued" + RUNNING = "Running" + + +RUN_STATE_MAP = { + 0: RunStates.NOT_RUNNING, + 1: RunStates.RUNNING, + 2: RunStates.QUEUED, +} diff --git a/homeassistant/components/rainmachine/model.py b/homeassistant/components/rainmachine/model.py index cd66c05025b..9f638d486aa 100644 --- a/homeassistant/components/rainmachine/model.py +++ b/homeassistant/components/rainmachine/model.py @@ -3,7 +3,14 @@ from dataclasses import dataclass @dataclass -class RainMachineSensorDescriptionMixin: +class RainMachineDescriptionMixinApiCategory: """Define an entity description mixin for binary and regular sensors.""" api_category: str + + +@dataclass +class RainMachineDescriptionMixinUid: + """Define an entity description mixin for switches.""" + + uid: int diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index 2db16ec9058..b825faca7e1 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations from dataclasses import dataclass +from datetime import datetime, timedelta from functools import partial from homeassistant.components.sensor import ( @@ -15,6 +16,7 @@ from homeassistant.const import TEMP_CELSIUS, VOLUME_CUBIC_METERS from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.dt import utcnow from . import RainMachineEntity from .const import ( @@ -22,26 +24,42 @@ from .const import ( DATA_COORDINATOR, DATA_PROVISION_SETTINGS, DATA_RESTRICTIONS_UNIVERSAL, + DATA_ZONES, DOMAIN, + RUN_STATE_MAP, + RunStates, ) -from .model import RainMachineSensorDescriptionMixin +from .model import ( + RainMachineDescriptionMixinApiCategory, + RainMachineDescriptionMixinUid, +) + +DEFAULT_ZONE_COMPLETION_TIME_WOBBLE_TOLERANCE = timedelta(seconds=5) TYPE_FLOW_SENSOR_CLICK_M3 = "flow_sensor_clicks_cubic_meter" TYPE_FLOW_SENSOR_CONSUMED_LITERS = "flow_sensor_consumed_liters" TYPE_FLOW_SENSOR_START_INDEX = "flow_sensor_start_index" TYPE_FLOW_SENSOR_WATERING_CLICKS = "flow_sensor_watering_clicks" TYPE_FREEZE_TEMP = "freeze_protect_temp" +TYPE_ZONE_RUN_COMPLETION_TIME = "zone_run_completion_time" @dataclass -class RainMachineSensorEntityDescription( - SensorEntityDescription, RainMachineSensorDescriptionMixin +class RainMachineSensorDescriptionApiCategory( + SensorEntityDescription, RainMachineDescriptionMixinApiCategory +): + """Describe a RainMachine sensor.""" + + +@dataclass +class RainMachineSensorDescriptionUid( + SensorEntityDescription, RainMachineDescriptionMixinUid ): """Describe a RainMachine sensor.""" SENSOR_DESCRIPTIONS = ( - RainMachineSensorEntityDescription( + RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_CLICK_M3, name="Flow Sensor Clicks per Cubic Meter", icon="mdi:water-pump", @@ -51,7 +69,7 @@ SENSOR_DESCRIPTIONS = ( state_class=SensorStateClass.MEASUREMENT, api_category=DATA_PROVISION_SETTINGS, ), - RainMachineSensorEntityDescription( + RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_CONSUMED_LITERS, name="Flow Sensor Consumed Liters", icon="mdi:water-pump", @@ -61,7 +79,7 @@ SENSOR_DESCRIPTIONS = ( state_class=SensorStateClass.TOTAL_INCREASING, api_category=DATA_PROVISION_SETTINGS, ), - RainMachineSensorEntityDescription( + RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_START_INDEX, name="Flow Sensor Start Index", icon="mdi:water-pump", @@ -70,7 +88,7 @@ SENSOR_DESCRIPTIONS = ( entity_registry_enabled_default=False, api_category=DATA_PROVISION_SETTINGS, ), - RainMachineSensorEntityDescription( + RainMachineSensorDescriptionApiCategory( key=TYPE_FLOW_SENSOR_WATERING_CLICKS, name="Flow Sensor Clicks", icon="mdi:water-pump", @@ -80,7 +98,7 @@ SENSOR_DESCRIPTIONS = ( state_class=SensorStateClass.MEASUREMENT, api_category=DATA_PROVISION_SETTINGS, ), - RainMachineSensorEntityDescription( + RainMachineSensorDescriptionApiCategory( key=TYPE_FREEZE_TEMP, name="Freeze Protect Temperature", icon="mdi:thermometer", @@ -101,7 +119,7 @@ async def async_setup_entry( coordinators = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] @callback - def async_get_sensor(api_category: str) -> partial: + def async_get_sensor_by_api_category(api_category: str) -> partial: """Generate the appropriate sensor object for an API category.""" if api_category == DATA_PROVISION_SETTINGS: return partial( @@ -116,12 +134,31 @@ async def async_setup_entry( coordinators[DATA_RESTRICTIONS_UNIVERSAL], ) - async_add_entities( - [ - async_get_sensor(description.api_category)(controller, description) - for description in SENSOR_DESCRIPTIONS - ] - ) + sensors = [ + async_get_sensor_by_api_category(description.api_category)( + controller, description + ) + for description in SENSOR_DESCRIPTIONS + ] + + zone_coordinator = coordinators[DATA_ZONES] + for uid, zone in zone_coordinator.data.items(): + sensors.append( + ZoneTimeRemainingSensor( + entry, + zone_coordinator, + controller, + RainMachineSensorDescriptionUid( + key=f"{TYPE_ZONE_RUN_COMPLETION_TIME}_{uid}", + name=f"{zone['name']} Run Completion Time", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + uid=uid, + ), + ) + ) + + async_add_entities(sensors) class ProvisionSettingsSensor(RainMachineEntity, SensorEntity): @@ -162,3 +199,32 @@ class UniversalRestrictionsSensor(RainMachineEntity, SensorEntity): """Update the state.""" if self.entity_description.key == TYPE_FREEZE_TEMP: self._attr_native_value = self.coordinator.data["freezeProtectTemp"] + + +class ZoneTimeRemainingSensor(RainMachineEntity, SensorEntity): + """Define a sensor that shows the amount of time remaining for a zone.""" + + entity_description: RainMachineSensorDescriptionUid + + @callback + def update_from_latest_data(self) -> None: + """Update the state.""" + data = self.coordinator.data[self.entity_description.uid] + now = utcnow() + + if RUN_STATE_MAP.get(data["state"]) != RunStates.RUNNING: + # If the zone isn't actively running, return immediately: + return + + new_timestamp = now + timedelta(seconds=data["remaining"]) + + if self._attr_native_value: + assert isinstance(self._attr_native_value, datetime) + if ( + new_timestamp - self._attr_native_value + ) < DEFAULT_ZONE_COMPLETION_TIME_WOBBLE_TOLERANCE: + # If the deviation between the previous and new timestamps is less than + # a "wobble tolerance," don't spam the state machine: + return + + self._attr_native_value = new_timestamp diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py index 523e4f94640..007aec97a3e 100644 --- a/homeassistant/components/rainmachine/switch.py +++ b/homeassistant/components/rainmachine/switch.py @@ -30,7 +30,9 @@ from .const import ( DATA_ZONES, DEFAULT_ZONE_RUN, DOMAIN, + RUN_STATE_MAP, ) +from .model import RainMachineDescriptionMixinUid ATTR_AREA = "area" ATTR_CS_ON = "cs_on" @@ -49,14 +51,11 @@ ATTR_SOIL_TYPE = "soil_type" ATTR_SPRINKLER_TYPE = "sprinkler_head_type" ATTR_STATUS = "status" ATTR_SUN_EXPOSURE = "sun_exposure" -ATTR_TIME_REMAINING = "time_remaining" ATTR_VEGETATION_TYPE = "vegetation_type" ATTR_ZONES = "zones" DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] -RUN_STATUS_MAP = {0: "Not Running", 1: "Running", 2: "Queued"} - SOIL_TYPE_MAP = { 0: "Not Set", 1: "Clay Loam", @@ -109,16 +108,9 @@ VEGETATION_MAP = { } -@dataclass -class RainMachineSwitchDescriptionMixin: - """Define an entity description mixin for switches.""" - - uid: int - - @dataclass class RainMachineSwitchDescription( - SwitchEntityDescription, RainMachineSwitchDescriptionMixin + SwitchEntityDescription, RainMachineDescriptionMixinUid ): """Describe a RainMachine switch.""" @@ -335,7 +327,7 @@ class RainMachineProgram(RainMachineActivitySwitch): ATTR_ID: self.entity_description.uid, ATTR_NEXT_RUN: next_run, ATTR_SOAK: data.get("soak"), - ATTR_STATUS: RUN_STATUS_MAP[data["status"]], + ATTR_STATUS: RUN_STATE_MAP[data["status"]], ATTR_ZONES: [z for z in data["wateringTimes"] if z["active"]], } ) @@ -399,20 +391,19 @@ class RainMachineZone(RainMachineActivitySwitch): self._attr_extra_state_attributes.update( { - ATTR_AREA: data.get("waterSense").get("area"), - ATTR_CURRENT_CYCLE: data.get("cycle"), - ATTR_FIELD_CAPACITY: data.get("waterSense").get("fieldCapacity"), + ATTR_AREA: round(data["waterSense"]["area"], 2), + ATTR_CURRENT_CYCLE: data["cycle"], + ATTR_FIELD_CAPACITY: round(data["waterSense"]["fieldCapacity"], 2), ATTR_ID: data["uid"], - ATTR_NO_CYCLES: data.get("noOfCycles"), - ATTR_PRECIP_RATE: data.get("waterSense").get("precipitationRate"), - ATTR_RESTRICTIONS: data.get("restriction"), - ATTR_SLOPE: SLOPE_TYPE_MAP.get(data.get("slope")), - ATTR_SOIL_TYPE: SOIL_TYPE_MAP.get(data.get("soil")), - ATTR_SPRINKLER_TYPE: SPRINKLER_TYPE_MAP.get(data.get("group_id")), - ATTR_STATUS: RUN_STATUS_MAP[data["state"]], + ATTR_NO_CYCLES: data["noOfCycles"], + ATTR_PRECIP_RATE: round(data["waterSense"]["precipitationRate"], 2), + ATTR_RESTRICTIONS: data["restriction"], + ATTR_SLOPE: SLOPE_TYPE_MAP.get(data["slope"], 99), + ATTR_SOIL_TYPE: SOIL_TYPE_MAP.get(data["soil"], 99), + ATTR_SPRINKLER_TYPE: SPRINKLER_TYPE_MAP.get(data["group_id"], 99), + ATTR_STATUS: RUN_STATE_MAP[data["state"]], ATTR_SUN_EXPOSURE: SUN_EXPOSURE_MAP.get(data.get("sun")), - ATTR_TIME_REMAINING: data.get("remaining"), - ATTR_VEGETATION_TYPE: VEGETATION_MAP.get(data.get("type")), + ATTR_VEGETATION_TYPE: VEGETATION_MAP.get(data["type"], 99), } ) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 0381e5a4671..1ff7c886c35 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -14,12 +14,20 @@ import time from typing import Any, TypeVar, cast from lru import LRU # pylint: disable=no-name-in-module -from sqlalchemy import create_engine, event as sqlalchemy_event, exc, func, select +from sqlalchemy import ( + bindparam, + create_engine, + event as sqlalchemy_event, + exc, + func, + select, +) from sqlalchemy.engine import Engine from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.ext import baked from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.orm.query import Query from sqlalchemy.orm.session import Session -from sqlalchemy.pool import StaticPool import voluptuous as vol from homeassistant.components import persistent_notification @@ -30,7 +38,6 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, MATCH_ALL, ) from homeassistant.core import ( @@ -72,20 +79,22 @@ from .const import ( ) from .executor import DBInterruptibleThreadPoolExecutor from .models import ( + SCHEMA_VERSION, Base, Events, - RecorderRuns, StateAttributes, States, StatisticsRuns, process_timestamp, ) -from .pool import POOL_SIZE, RecorderPool +from .pool import POOL_SIZE, MutexPool, RecorderPool +from .run_history import RunHistory from .util import ( dburl_to_path, end_incomplete_runs, + is_second_sunday, move_away_broken_database, - perodic_db_cleanups, + periodic_db_cleanups, session_scope, setup_connection_for_dialect, validate_or_move_away_sqlite_database, @@ -156,6 +165,7 @@ DB_LOCK_TIMEOUT = 30 DB_LOCK_QUEUE_CHECK_TIMEOUT = 1 CONF_AUTO_PURGE = "auto_purge" +CONF_AUTO_REPACK = "auto_repack" CONF_DB_URL = "db_url" CONF_DB_MAX_RETRIES = "db_max_retries" CONF_DB_RETRY_WAIT = "db_retry_wait" @@ -175,6 +185,19 @@ FILTER_SCHEMA = INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend( {vol.Optional(CONF_EXCLUDE, default=EXCLUDE_SCHEMA({})): EXCLUDE_SCHEMA} ) + +ALLOW_IN_MEMORY_DB = False + + +def validate_db_url(db_url: str) -> Any: + """Validate database URL.""" + # Don't allow on-memory sqlite databases + if (db_url == SQLITE_URL_PREFIX or ":memory:" in db_url) and not ALLOW_IN_MEMORY_DB: + raise vol.Invalid("In-memory SQLite database is not supported") + + return db_url + + CONFIG_SCHEMA = vol.Schema( { vol.Optional(DOMAIN, default=dict): vol.All( @@ -183,11 +206,12 @@ CONFIG_SCHEMA = vol.Schema( FILTER_SCHEMA.extend( { vol.Optional(CONF_AUTO_PURGE, default=True): cv.boolean, + vol.Optional(CONF_AUTO_REPACK, default=True): cv.boolean, vol.Optional(CONF_PURGE_KEEP_DAYS, default=10): vol.All( vol.Coerce(int), vol.Range(min=1) ), vol.Optional(CONF_PURGE_INTERVAL, default=1): cv.positive_int, - vol.Optional(CONF_DB_URL): cv.string, + vol.Optional(CONF_DB_URL): vol.All(cv.string, validate_db_url), vol.Optional( CONF_COMMIT_INTERVAL, default=DEFAULT_COMMIT_INTERVAL ): cv.positive_int, @@ -230,51 +254,6 @@ def is_entity_recorded(hass: HomeAssistant, entity_id: str) -> bool: return instance.entity_filter(entity_id) -def run_information( - hass: HomeAssistant, point_in_time: datetime | None = None -) -> RecorderRuns | None: - """Return information about current run. - - There is also the run that covers point_in_time. - """ - if run_info := run_information_from_instance(hass, point_in_time): - return run_info - - with session_scope(hass=hass) as session: - return run_information_with_session(session, point_in_time) - - -def run_information_from_instance( - hass: HomeAssistant, point_in_time: datetime | None = None -) -> RecorderRuns | None: - """Return information about current run from the existing instance. - - Does not query the database for older runs. - """ - ins = get_instance(hass) - if point_in_time is None or point_in_time > ins.recording_start: - return ins.run_info - return None - - -def run_information_with_session( - session: Session, point_in_time: datetime | None = None -) -> RecorderRuns | None: - """Return information about current run from the database.""" - recorder_runs = RecorderRuns - - query = session.query(recorder_runs) - if point_in_time: - query = query.filter( - (recorder_runs.start < point_in_time) & (recorder_runs.end > point_in_time) - ) - - if (res := query.first()) is not None: - session.expunge(res) - return cast(RecorderRuns, res) - return res - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the recorder.""" hass.data[DOMAIN] = {} @@ -283,6 +262,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: conf = config[DOMAIN] entity_filter = convert_include_exclude_filter(conf) auto_purge = conf[CONF_AUTO_PURGE] + auto_repack = conf[CONF_AUTO_REPACK] keep_days = conf[CONF_PURGE_KEEP_DAYS] commit_interval = conf[CONF_COMMIT_INTERVAL] db_max_retries = conf[CONF_DB_MAX_RETRIES] @@ -300,6 +280,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: instance = hass.data[DATA_INSTANCE] = Recorder( hass=hass, auto_purge=auto_purge, + auto_repack=auto_repack, keep_days=keep_days, commit_interval=commit_interval, uri=db_url, @@ -308,6 +289,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: entity_filter=entity_filter, exclude_t=exclude_t, exclude_attributes_by_domain=exclude_attributes_by_domain, + bakery=baked.bakery(), ) instance.async_initialize() instance.async_register() @@ -325,10 +307,8 @@ async def _process_recorder_platform( hass: HomeAssistant, domain: str, platform: Any ) -> None: """Process a recorder platform.""" - platforms: dict[str, Any] = hass.data[DOMAIN] - platforms[domain] = platform - if hasattr(platform, "exclude_attributes"): - hass.data[EXCLUDE_ATTRIBUTES][domain] = platform.exclude_attributes(hass) + instance: Recorder = hass.data[DATA_INSTANCE] + instance.queue.put(AddRecorderPlatformTask(domain, platform)) @callback @@ -424,13 +404,17 @@ class PurgeTask(RecorderTask): def run(self, instance: Recorder) -> None: """Purge the database.""" + assert instance.get_session is not None + if purge.purge_old_data( instance, self.purge_before, self.repack, self.apply_filter ): + with instance.get_session() as session: + instance.run_history.load_from_db(session) # We always need to do the db cleanups after a purge # is finished to ensure the WAL checkpoint and other # tasks happen after a vacuum. - perodic_db_cleanups(instance) + periodic_db_cleanups(instance) return # Schedule a new purge task if this one didn't finish instance.queue.put(PurgeTask(self.purge_before, self.repack, self.apply_filter)) @@ -456,7 +440,7 @@ class PerodicCleanupTask(RecorderTask): def run(self, instance: Recorder) -> None: """Handle the task.""" - perodic_db_cleanups(instance) + periodic_db_cleanups(instance) @dataclass @@ -561,6 +545,54 @@ class EventTask(RecorderTask): instance._process_one_event(self.event) +@dataclass +class KeepAliveTask(RecorderTask): + """A keep alive to be sent.""" + + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + # pylint: disable-next=[protected-access] + instance._send_keep_alive() + + +@dataclass +class CommitTask(RecorderTask): + """Commit the event session.""" + + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + # pylint: disable-next=[protected-access] + instance._commit_event_session_or_retry() + + +@dataclass +class AddRecorderPlatformTask(RecorderTask): + """Add a recorder platform.""" + + domain: str + platform: Any + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + hass = instance.hass + domain = self.domain + platform = self.platform + + platforms: dict[str, Any] = hass.data[DOMAIN] + platforms[domain] = platform + if hasattr(self.platform, "exclude_attributes"): + hass.data[EXCLUDE_ATTRIBUTES][domain] = platform.exclude_attributes(hass) + + +COMMIT_TASK = CommitTask() +KEEP_ALIVE_TASK = KeepAliveTask() + + class Recorder(threading.Thread): """A threaded recorder class.""" @@ -570,6 +602,7 @@ class Recorder(threading.Thread): self, hass: HomeAssistant, auto_purge: bool, + auto_repack: bool, keep_days: int, commit_interval: int, uri: str, @@ -578,17 +611,18 @@ class Recorder(threading.Thread): entity_filter: Callable[[str], bool], exclude_t: list[str], exclude_attributes_by_domain: dict[str, set[str]], + bakery: baked.bakery, ) -> None: """Initialize the recorder.""" threading.Thread.__init__(self, name="Recorder") self.hass = hass self.auto_purge = auto_purge + self.auto_repack = auto_repack self.keep_days = keep_days self._hass_started: asyncio.Future[object] = asyncio.Future() self.commit_interval = commit_interval self.queue: queue.SimpleQueue[RecorderTask] = queue.SimpleQueue() - self.recording_start = dt_util.utcnow() self.db_url = uri self.db_max_retries = db_max_retries self.db_retry_wait = db_retry_wait @@ -596,18 +630,19 @@ class Recorder(threading.Thread): self.async_recorder_ready = asyncio.Event() self._queue_watch = threading.Event() self.engine: Engine | None = None - self.run_info: RecorderRuns | None = None + self.run_history = RunHistory() self.entity_filter = entity_filter self.exclude_t = exclude_t - self._timechanges_seen = 0 + self.schema_version = 0 self._commits_without_expire = 0 - self._keepalive_count = 0 self._old_states: dict[str, States] = {} self._state_attributes_ids: LRU = LRU(STATE_ATTRIBUTES_ID_CACHE_SIZE) self._pending_state_attributes: dict[str, StateAttributes] = {} self._pending_expunge: list[States] = [] + self._bakery = bakery + self._find_shared_attr_query: Query | None = None self.event_session: Session | None = None self.get_session: Callable[[], Session] | None = None self._completed_first_database_setup: bool | None = None @@ -620,6 +655,10 @@ class Recorder(threading.Thread): self._db_executor: DBInterruptibleThreadPoolExecutor | None = None self._exclude_attributes_by_domain = exclude_attributes_by_domain + self._keep_alive_listener: CALLBACK_TYPE | None = None + self._commit_listener: CALLBACK_TYPE | None = None + self._periodic_listener: CALLBACK_TYPE | None = None + self._nightly_listener: CALLBACK_TYPE | None = None self.enabled = True def set_enable(self, enable: bool) -> None: @@ -650,6 +689,22 @@ class Recorder(threading.Thread): self.hass, self._async_check_queue, timedelta(minutes=10) ) + @callback + def _async_keep_alive(self, now: datetime) -> None: + """Queue a keep alive.""" + if self._event_listener: + self.queue.put(KEEP_ALIVE_TASK) + + @callback + def _async_commit(self, now: datetime) -> None: + """Queue a commit.""" + if ( + self._event_listener + and not self._database_lock_task + and self._event_session_has_pending_writes() + ): + self.queue.put(COMMIT_TASK) + @callback def async_add_executor_job( self, target: Callable[..., T], *args: Any @@ -671,10 +726,13 @@ class Recorder(threading.Thread): """ size = self.queue.qsize() _LOGGER.debug("Recorder queue size is: %s", size) - if self.queue.qsize() <= MAX_QUEUE_BACKLOG: + if size <= MAX_QUEUE_BACKLOG: return _LOGGER.error( - "The recorder queue reached the maximum size of %s; Events are no longer being recorded", + "The recorder backlog queue reached the maximum size of %s events; " + "usually, the system is CPU bound, I/O bound, or the database " + "is corrupt due to a disk problem; The recorder will stop " + "recording events to avoid running out of memory", MAX_QUEUE_BACKLOG, ) self._async_stop_queue_watcher_and_event_listener() @@ -689,6 +747,23 @@ class Recorder(threading.Thread): self._event_listener() self._event_listener = None + @callback + def _async_stop_listeners(self) -> None: + """Stop listeners.""" + self._async_stop_queue_watcher_and_event_listener() + if self._keep_alive_listener: + self._keep_alive_listener() + self._keep_alive_listener = None + if self._commit_listener: + self._commit_listener() + self._commit_listener = None + if self._nightly_listener: + self._nightly_listener() + self._nightly_listener = None + if self._periodic_listener: + self._periodic_listener() + self._periodic_listener = None + @callback def _async_event_filter(self, event: Event) -> bool: """Filter events.""" @@ -760,7 +835,7 @@ class Recorder(threading.Thread): if not self._hass_started.done(): self._hass_started.set_result(SHUTDOWN_TASK) self.queue.put(StopTask()) - self._async_stop_queue_watcher_and_event_listener() + self._async_stop_listeners() await self.hass.async_add_executor_job(self.join) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown) @@ -787,7 +862,7 @@ class Recorder(threading.Thread): "The recorder could not start, check [the logs](/config/logs)", "Recorder", ) - self._async_stop_queue_watcher_and_event_listener() + self._async_stop_listeners() @callback def async_connection_success(self) -> None: @@ -805,17 +880,21 @@ class Recorder(threading.Thread): def async_nightly_tasks(self, now: datetime) -> None: """Trigger the purge.""" if self.auto_purge: - # Purge will schedule the perodic cleanups + # Purge will schedule the periodic cleanups # after it completes to ensure it does not happen # until after the database is vacuumed + repack = self.auto_repack and is_second_sunday(now) purge_before = dt_util.utcnow() - timedelta(days=self.keep_days) - self.queue.put(PurgeTask(purge_before, repack=False, apply_filter=False)) + self.queue.put(PurgeTask(purge_before, repack=repack, apply_filter=False)) else: self.queue.put(PerodicCleanupTask()) @callback def async_periodic_statistics(self, now: datetime) -> None: - """Trigger the hourly statistics run.""" + """Trigger the statistics run. + + Short term statistics run every 5 minutes + """ start = statistics.get_start_time() self.queue.put(StatisticsTask(start)) @@ -843,6 +922,11 @@ class Recorder(threading.Thread): """Schedule external statistics.""" self.queue.put(ExternalStatisticsTask(metadata, stats)) + @callback + def using_sqlite(self) -> bool: + """Return if recorder uses sqlite as the engine.""" + return bool(self.engine and self.engine.dialect.name == "sqlite") + @callback def _async_setup_periodic_tasks(self) -> None: """Prepare periodic tasks.""" @@ -850,13 +934,26 @@ class Recorder(threading.Thread): # Home Assistant is shutting down return + # If the db is using a socket connection, we need to keep alive + # to prevent errors from unexpected disconnects + if not self.using_sqlite(): + self._keep_alive_listener = async_track_time_interval( + self.hass, self._async_keep_alive, timedelta(seconds=KEEPALIVE_TIME) + ) + + # If the commit interval is not 0, we need to commit periodically + if self.commit_interval: + self._commit_listener = async_track_time_interval( + self.hass, self._async_commit, timedelta(seconds=self.commit_interval) + ) + # Run nightly tasks at 4:12am - async_track_time_change( + self._nightly_listener = async_track_time_change( self.hass, self.async_nightly_tasks, hour=4, minute=12, second=0 ) # Compile short term statistics every 5 minutes - async_track_utc_time_change( + self._periodic_listener = async_track_utc_time_change( self.hass, self.async_periodic_statistics, minute=range(0, 60, 5), second=10 ) @@ -878,6 +975,8 @@ class Recorder(threading.Thread): self.hass.add_job(self.async_connection_failed) return + self.schema_version = current_version + schema_is_current = migration.schema_is_current(current_version) if schema_is_current: self._setup_run() @@ -899,6 +998,7 @@ class Recorder(threading.Thread): # with startup which is also cpu intensive if not schema_is_current: if self._migrate_schema_and_setup_run(current_version): + self.schema_version = SCHEMA_VERSION if not self._event_listener: # If the schema migration takes so long that the end # queue watcher safety kicks in because MAX_QUEUE_BACKLOG @@ -926,6 +1026,7 @@ class Recorder(threading.Thread): self.stop_requested = False while not self.stop_requested: task = self.queue.get() + _LOGGER.debug("Processing task: %s", task) try: self._process_one_task_or_recover(task) except Exception as err: # pylint: disable=broad-except @@ -1029,20 +1130,40 @@ class Recorder(threading.Thread): ) def _process_one_event(self, event: Event) -> None: - if event.event_type == EVENT_TIME_CHANGED: - self._keepalive_count += 1 - if self._keepalive_count >= KEEPALIVE_TIME: - self._keepalive_count = 0 - self._send_keep_alive() - if self.commit_interval: - self._timechanges_seen += 1 - if self._timechanges_seen >= self.commit_interval: - self._timechanges_seen = 0 - self._commit_event_session_or_retry() - return - if not self.enabled: return + self._process_event_into_session(event) + # Commit if the commit interval is zero + if not self.commit_interval: + self._commit_event_session_or_retry() + + def _find_shared_attr_in_db(self, attr_hash: int, shared_attrs: str) -> int | None: + """Find shared attributes in the db from the hash and shared_attrs.""" + # + # Avoid the event session being flushed since it will + # commit all the pending events and states to the database. + # + # The lookup has already have checked to see if the data is cached + # or going to be written in the next commit so there is no + # need to flush before checking the database. + # + assert self.event_session is not None + if self._find_shared_attr_query is None: + self._find_shared_attr_query = self._bakery( + lambda session: session.query(StateAttributes.attributes_id) + .filter(StateAttributes.hash == bindparam("attr_hash")) + .filter(StateAttributes.shared_attrs == bindparam("shared_attrs")) + ) + with self.event_session.no_autoflush: + if ( + attributes := self._find_shared_attr_query(self.event_session) + .params(attr_hash=attr_hash, shared_attrs=shared_attrs) + .first() + ): + return cast(int, attributes[0]) + return None + + def _process_event_into_session(self, event: Event) -> None: assert self.event_session is not None try: @@ -1055,66 +1176,56 @@ class Recorder(threading.Thread): return self.event_session.add(dbevent) - if event.event_type == EVENT_STATE_CHANGED: - try: - dbstate = States.from_event(event) - shared_attrs = StateAttributes.shared_attrs_from_event( - event, self._exclude_attributes_by_domain - ) - except (TypeError, ValueError) as ex: - _LOGGER.warning( - "State is not JSON serializable: %s: %s", - event.data.get("new_state"), - ex, - ) - return + if event.event_type != EVENT_STATE_CHANGED: + return - dbstate.attributes = None - # Matching attributes found in the pending commit - if pending_attributes := self._pending_state_attributes.get(shared_attrs): - dbstate.state_attributes = pending_attributes - # Matching attributes id found in the cache - elif attributes_id := self._state_attributes_ids.get(shared_attrs): + try: + dbstate = States.from_event(event) + shared_attrs = StateAttributes.shared_attrs_from_event( + event, self._exclude_attributes_by_domain + ) + except (TypeError, ValueError) as ex: + _LOGGER.warning( + "State is not JSON serializable: %s: %s", + event.data.get("new_state"), + ex, + ) + return + + dbstate.attributes = None + # Matching attributes found in the pending commit + if pending_attributes := self._pending_state_attributes.get(shared_attrs): + dbstate.state_attributes = pending_attributes + # Matching attributes id found in the cache + elif attributes_id := self._state_attributes_ids.get(shared_attrs): + dbstate.attributes_id = attributes_id + else: + attr_hash = StateAttributes.hash_shared_attrs(shared_attrs) + # Matching attributes found in the database + if attributes_id := self._find_shared_attr_in_db(attr_hash, shared_attrs): dbstate.attributes_id = attributes_id + self._state_attributes_ids[shared_attrs] = attributes_id + # No matching attributes found, save them in the DB else: - attr_hash = StateAttributes.hash_shared_attrs(shared_attrs) - # Matching attributes found in the database - if ( - attributes := self.event_session.query( - StateAttributes.attributes_id - ) - .filter(StateAttributes.hash == attr_hash) - .filter(StateAttributes.shared_attrs == shared_attrs) - .first() - ): - dbstate.attributes_id = attributes[0] - self._state_attributes_ids[shared_attrs] = attributes[0] - # No matching attributes found, save them in the DB - else: - dbstate_attributes = StateAttributes( - shared_attrs=shared_attrs, hash=attr_hash - ) - dbstate.state_attributes = dbstate_attributes - self._pending_state_attributes[shared_attrs] = dbstate_attributes - self.event_session.add(dbstate_attributes) + dbstate_attributes = StateAttributes( + shared_attrs=shared_attrs, hash=attr_hash + ) + dbstate.state_attributes = dbstate_attributes + self._pending_state_attributes[shared_attrs] = dbstate_attributes + self.event_session.add(dbstate_attributes) - if old_state := self._old_states.pop(dbstate.entity_id, None): - if old_state.state_id: - dbstate.old_state_id = old_state.state_id - else: - dbstate.old_state = old_state - if event.data.get("new_state"): - self._old_states[dbstate.entity_id] = dbstate - self._pending_expunge.append(dbstate) + if old_state := self._old_states.pop(dbstate.entity_id, None): + if old_state.state_id: + dbstate.old_state_id = old_state.state_id else: - dbstate.state = None - self.event_session.add(dbstate) - dbstate.event = dbevent - - # If they do not have a commit interval - # than we commit right away - if not self.commit_interval: - self._commit_event_session_or_retry() + dbstate.old_state = old_state + if event.data.get("new_state"): + self._old_states[dbstate.entity_id] = dbstate + self._pending_expunge.append(dbstate) + else: + dbstate.state = None + dbstate.event = dbevent + self.event_session.add(dbstate) def _handle_database_error(self, err: Exception) -> bool: """Handle a database error that may result in moving away the corrupt db.""" @@ -1126,11 +1237,14 @@ class Recorder(threading.Thread): return True return False + def _event_session_has_pending_writes(self) -> bool: + return bool( + self.event_session and (self.event_session.new or self.event_session.dirty) + ) + def _commit_event_session_or_retry(self) -> None: """Commit the event session if there is work to do.""" - if not self.event_session or ( - not self.event_session.new and not self.event_session.dirty - ): + if not self._event_session_has_pending_writes(): return tries = 1 while tries <= self.db_max_retries: @@ -1177,7 +1291,7 @@ class Recorder(threading.Thread): # Expire is an expensive operation (frequently more expensive # than the flush and commit itself) so we only # do it after EXPIRE_AFTER_COMMITS commits - if self._commits_without_expire == EXPIRE_AFTER_COMMITS: + if self._commits_without_expire >= EXPIRE_AFTER_COMMITS: self._commits_without_expire = 0 self.event_session.expire_all() @@ -1186,6 +1300,7 @@ class Recorder(threading.Thread): self._close_event_session() self._close_connection() move_away_broken_database(dburl_to_path(self.db_url)) + self.run_history.reset() self._setup_recorder() self._setup_run() @@ -1246,7 +1361,7 @@ class Recorder(threading.Thread): async def lock_database(self) -> bool: """Lock database so it can be backed up safely.""" - if not self.engine or self.engine.dialect.name != "sqlite": + if not self.using_sqlite(): _LOGGER.debug( "Not a SQLite database or not connected, locking not necessary" ) @@ -1275,7 +1390,7 @@ class Recorder(threading.Thread): Returns true if database lock has been held throughout the process. """ - if not self.engine or self.engine.dialect.name != "sqlite": + if not self.using_sqlite(): _LOGGER.debug( "Not a SQLite database or not connected, unlocking not necessary" ) @@ -1312,7 +1427,8 @@ class Recorder(threading.Thread): if self.db_url == SQLITE_URL_PREFIX or ":memory:" in self.db_url: kwargs["connect_args"] = {"check_same_thread": False} - kwargs["poolclass"] = StaticPool + kwargs["poolclass"] = MutexPool + MutexPool.pool_lock = threading.RLock() kwargs["pool_reset_on_return"] = None elif self.db_url.startswith(SQLITE_URL_PREFIX): kwargs["poolclass"] = RecorderPool @@ -1322,12 +1438,12 @@ class Recorder(threading.Thread): if self._using_file_sqlite: validate_or_move_away_sqlite_database(self.db_url) - self.engine = create_engine(self.db_url, **kwargs) + self.engine = create_engine(self.db_url, **kwargs, future=True) sqlalchemy_event.listen(self.engine, "connect", setup_recorder_connection) Base.metadata.create_all(self.engine) - self.get_session = scoped_session(sessionmaker(bind=self.engine)) + self.get_session = scoped_session(sessionmaker(bind=self.engine, future=True)) _LOGGER.debug("Connected to recorder database") @property @@ -1348,12 +1464,8 @@ class Recorder(threading.Thread): """Log the start of the current run and schedule any needed jobs.""" assert self.get_session is not None with session_scope(session=self.get_session()) as session: - start = self.recording_start - end_incomplete_runs(session, start) - self.run_info = RecorderRuns(start=start, created=dt_util.utcnow()) - session.add(self.run_info) - session.flush() - session.expunge(self.run_info) + end_incomplete_runs(session, self.run_history.recording_start) + self.run_history.start(session) self._schedule_compile_missing_statistics(session) self._open_event_session() @@ -1381,20 +1493,18 @@ class Recorder(threading.Thread): """End the recorder session.""" if self.event_session is None: return - assert self.run_info is not None try: - self.run_info.end = dt_util.utcnow() - self.event_session.add(self.run_info) + self.run_history.end(self.event_session) self._commit_event_session_or_retry() self.event_session.close() except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Error saving the event session during shutdown: %s", err) - self.run_info = None + self.run_history.clear() def _shutdown(self) -> None: """Save end time for current run.""" - self.hass.add_job(self._async_stop_queue_watcher_and_event_listener) + self.hass.add_job(self._async_stop_listeners) self._stop_executor() self._end_session() self._close_connection() diff --git a/homeassistant/components/recorder/const.py b/homeassistant/components/recorder/const.py index 0fce9657624..593710a10dd 100644 --- a/homeassistant/components/recorder/const.py +++ b/homeassistant/components/recorder/const.py @@ -4,7 +4,7 @@ from functools import partial import json from typing import Final -from homeassistant.const import ATTR_ATTRIBUTION, ATTR_SUPPORTED_FEATURES +from homeassistant.const import ATTR_ATTRIBUTION, ATTR_RESTORED, ATTR_SUPPORTED_FEATURES from homeassistant.helpers.json import JSONEncoder DATA_INSTANCE = "recorder_instance" @@ -13,7 +13,7 @@ DOMAIN = "recorder" CONF_DB_INTEGRITY_CHECK = "db_integrity_check" -MAX_QUEUE_BACKLOG = 30000 +MAX_QUEUE_BACKLOG = 40000 # The maximum number of rows (events) we purge in one delete statement @@ -27,4 +27,4 @@ DB_WORKER_PREFIX = "DbWorker" JSON_DUMP: Final = partial(json.dumps, cls=JSONEncoder, separators=(",", ":")) -ALL_DOMAIN_EXCLUDE_ATTRS = {ATTR_ATTRIBUTION, ATTR_SUPPORTED_FEATURES} +ALL_DOMAIN_EXCLUDE_ATTRS = {ATTR_ATTRIBUTION, ATTR_RESTORED, ATTR_SUPPORTED_FEATURES} diff --git a/homeassistant/components/recorder/history.py b/homeassistant/components/recorder/history.py index 95f69bf769f..d221ced3a84 100644 --- a/homeassistant/components/recorder/history.py +++ b/homeassistant/components/recorder/history.py @@ -2,12 +2,12 @@ from __future__ import annotations from collections import defaultdict -from collections.abc import Iterable, MutableMapping +from collections.abc import Iterable, Iterator, MutableMapping from datetime import datetime from itertools import groupby import logging import time -from typing import Any +from typing import Any, cast from sqlalchemy import Column, Text, and_, bindparam, func, or_ from sqlalchemy.ext import baked @@ -23,6 +23,7 @@ from .models import ( RecorderRuns, StateAttributes, States, + process_timestamp, process_timestamp_to_utc_isoformat, ) from .util import execute, session_scope @@ -58,11 +59,22 @@ BASE_STATES = [ States.last_changed, States.last_updated, ] +BASE_STATES_NO_LAST_UPDATED = [ + States.entity_id, + States.state, + States.last_changed, + literal(value=None, type_=Text).label("last_updated"), +] QUERY_STATE_NO_ATTR = [ *BASE_STATES, literal(value=None, type_=Text).label("attributes"), literal(value=None, type_=Text).label("shared_attrs"), ] +QUERY_STATE_NO_ATTR_NO_LAST_UPDATED = [ + *BASE_STATES_NO_LAST_UPDATED, + literal(value=None, type_=Text).label("attributes"), + literal(value=None, type_=Text).label("shared_attrs"), +] # Remove QUERY_STATES_PRE_SCHEMA_25 # and the migration_in_progress check # once schema 26 is created @@ -71,12 +83,23 @@ QUERY_STATES_PRE_SCHEMA_25 = [ States.attributes, literal(value=None, type_=Text).label("shared_attrs"), ] +QUERY_STATES_PRE_SCHEMA_25_NO_LAST_UPDATED = [ + *BASE_STATES_NO_LAST_UPDATED, + States.attributes, + literal(value=None, type_=Text).label("shared_attrs"), +] QUERY_STATES = [ *BASE_STATES, # Remove States.attributes once all attributes are in StateAttributes.shared_attrs States.attributes, StateAttributes.shared_attrs, ] +QUERY_STATES_NO_LAST_UPDATED = [ + *BASE_STATES_NO_LAST_UPDATED, + # Remove States.attributes once all attributes are in StateAttributes.shared_attrs + States.attributes, + StateAttributes.shared_attrs, +] HISTORY_BAKERY = "recorder_history_bakery" @@ -93,7 +116,7 @@ def query_and_join_attributes( # If we in the process of migrating schema we do # not want to join the state_attributes table as we # do not know if it will be there yet - if recorder.get_instance(hass).migration_in_progress: + if recorder.get_instance(hass).schema_version < 25: return QUERY_STATES_PRE_SCHEMA_25, False # Finally if no migration is in progress and no_attributes # was not requested, we query both attributes columns and @@ -102,7 +125,7 @@ def query_and_join_attributes( def bake_query_and_join_attributes( - hass: HomeAssistant, no_attributes: bool + hass: HomeAssistant, no_attributes: bool, include_last_updated: bool = True ) -> tuple[Any, bool]: """Return the initial backed query and if StateAttributes should be joined. @@ -114,16 +137,35 @@ def bake_query_and_join_attributes( # without the attributes fields and do not join the # state_attributes table if no_attributes: - return bakery(lambda session: session.query(*QUERY_STATE_NO_ATTR)), False + if include_last_updated: + return bakery(lambda session: session.query(*QUERY_STATE_NO_ATTR)), False + return ( + bakery(lambda session: session.query(*QUERY_STATE_NO_ATTR_NO_LAST_UPDATED)), + False, + ) # If we in the process of migrating schema we do # not want to join the state_attributes table as we # do not know if it will be there yet - if recorder.get_instance(hass).migration_in_progress: - return bakery(lambda session: session.query(*QUERY_STATES_PRE_SCHEMA_25)), False + if recorder.get_instance(hass).schema_version < 25: + if include_last_updated: + return ( + bakery(lambda session: session.query(*QUERY_STATES_PRE_SCHEMA_25)), + False, + ) + return ( + bakery( + lambda session: session.query( + *QUERY_STATES_PRE_SCHEMA_25_NO_LAST_UPDATED + ) + ), + False, + ) # Finally if no migration is in progress and no_attributes # was not requested, we query both attributes columns and # join state_attributes - return bakery(lambda session: session.query(*QUERY_STATES)), True + if include_last_updated: + return bakery(lambda session: session.query(*QUERY_STATES)), True + return bakery(lambda session: session.query(*QUERY_STATES_NO_LAST_UPDATED)), True def async_setup(hass: HomeAssistant) -> None: @@ -141,7 +183,7 @@ def get_significant_states( significant_changes_only: bool = True, minimal_response: bool = False, no_attributes: bool = False, -) -> MutableMapping[str, Iterable[LazyState | State | dict[str, Any]]]: +) -> MutableMapping[str, list[State | dict[str, Any]]]: """Wrap get_significant_states_with_session with an sql session.""" with session_scope(hass=hass) as session: return get_significant_states_with_session( @@ -158,31 +200,20 @@ def get_significant_states( ) -def get_significant_states_with_session( +def _query_significant_states_with_session( hass: HomeAssistant, session: Session, start_time: datetime, end_time: datetime | None = None, entity_ids: list[str] | None = None, filters: Any = None, - include_start_time_state: bool = True, significant_changes_only: bool = True, - minimal_response: bool = False, no_attributes: bool = False, -) -> MutableMapping[str, Iterable[LazyState | State | dict[str, Any]]]: - """ - Return states changes during UTC period start_time - end_time. +) -> list[States]: + """Query the database for significant state changes.""" + if _LOGGER.isEnabledFor(logging.DEBUG): + timer_start = time.perf_counter() - entity_ids is an optional iterable of entities to include in the results. - - filters is an optional SQLAlchemy filter which will be applied to the database - queries unless entity_ids is given, in which case its ignored. - - Significant states are all states where there is a state change, - as well as all states from certain domains (for instance - thermostat so that we get current temperature in our graphs). - """ - timer_start = time.perf_counter() baked_query, join_attributes = bake_query_and_join_attributes(hass, no_attributes) if entity_ids is not None and len(entity_ids) == 1: @@ -190,6 +221,9 @@ def get_significant_states_with_session( significant_changes_only and split_entity_id(entity_ids[0])[0] not in SIGNIFICANT_DOMAINS ): + baked_query, join_attributes = bake_query_and_join_attributes( + hass, no_attributes, include_last_updated=False + ) baked_query += lambda q: q.filter( States.last_changed == States.last_updated ) @@ -240,6 +274,43 @@ def get_significant_states_with_session( elapsed = time.perf_counter() - timer_start _LOGGER.debug("get_significant_states took %fs", elapsed) + return states + + +def get_significant_states_with_session( + hass: HomeAssistant, + session: Session, + start_time: datetime, + end_time: datetime | None = None, + entity_ids: list[str] | None = None, + filters: Any = None, + include_start_time_state: bool = True, + significant_changes_only: bool = True, + minimal_response: bool = False, + no_attributes: bool = False, +) -> MutableMapping[str, list[State | dict[str, Any]]]: + """ + Return states changes during UTC period start_time - end_time. + + entity_ids is an optional iterable of entities to include in the results. + + filters is an optional SQLAlchemy filter which will be applied to the database + queries unless entity_ids is given, in which case its ignored. + + Significant states are all states where there is a state change, + as well as all states from certain domains (for instance + thermostat so that we get current temperature in our graphs). + """ + states = _query_significant_states_with_session( + hass, + session, + start_time, + end_time, + entity_ids, + filters, + significant_changes_only, + no_attributes, + ) return _sorted_states_to_dict( hass, session, @@ -253,6 +324,35 @@ def get_significant_states_with_session( ) +def get_full_significant_states_with_session( + hass: HomeAssistant, + session: Session, + start_time: datetime, + end_time: datetime | None = None, + entity_ids: list[str] | None = None, + filters: Any = None, + include_start_time_state: bool = True, + significant_changes_only: bool = True, + no_attributes: bool = False, +) -> MutableMapping[str, list[State]]: + """Variant of get_significant_states_with_session that does not return minimal responses.""" + return cast( + MutableMapping[str, list[State]], + get_significant_states_with_session( + hass=hass, + session=session, + start_time=start_time, + end_time=end_time, + entity_ids=entity_ids, + filters=filters, + include_start_time_state=include_start_time_state, + significant_changes_only=significant_changes_only, + minimal_response=False, + no_attributes=no_attributes, + ), + ) + + def state_changes_during_period( hass: HomeAssistant, start_time: datetime, @@ -262,11 +362,11 @@ def state_changes_during_period( descending: bool = False, limit: int | None = None, include_start_time_state: bool = True, -) -> MutableMapping[str, Iterable[LazyState | State | dict[str, Any]]]: +) -> MutableMapping[str, list[State]]: """Return states changes during UTC period start_time - end_time.""" with session_scope(hass=hass) as session: baked_query, join_attributes = bake_query_and_join_attributes( - hass, no_attributes + hass, no_attributes, include_last_updated=False ) baked_query += lambda q: q.filter( @@ -288,38 +388,50 @@ def state_changes_during_period( StateAttributes, States.attributes_id == StateAttributes.attributes_id ) - last_updated = States.last_updated.desc() if descending else States.last_updated - baked_query += lambda q: q.order_by(States.entity_id, last_updated) + if descending: + baked_query += lambda q: q.order_by( + States.entity_id, States.last_updated.desc() + ) + else: + baked_query += lambda q: q.order_by(States.entity_id, States.last_updated) if limit: - baked_query += lambda q: q.limit(limit) + baked_query += lambda q: q.limit(bindparam("limit")) states = execute( baked_query(session).params( - start_time=start_time, end_time=end_time, entity_id=entity_id + start_time=start_time, + end_time=end_time, + entity_id=entity_id, + limit=limit, ) ) entity_ids = [entity_id] if entity_id is not None else None - return _sorted_states_to_dict( - hass, - session, - states, - start_time, - entity_ids, - include_start_time_state=include_start_time_state, + return cast( + MutableMapping[str, list[State]], + _sorted_states_to_dict( + hass, + session, + states, + start_time, + entity_ids, + include_start_time_state=include_start_time_state, + ), ) def get_last_state_changes( hass: HomeAssistant, number_of_states: int, entity_id: str -) -> MutableMapping[str, Iterable[LazyState | State | dict[str, Any]]]: +) -> MutableMapping[str, list[State]]: """Return the last number_of_states.""" start_time = dt_util.utcnow() with session_scope(hass=hass) as session: - baked_query, join_attributes = bake_query_and_join_attributes(hass, False) + baked_query, join_attributes = bake_query_and_join_attributes( + hass, False, include_last_updated=False + ) baked_query += lambda q: q.filter(States.last_changed == States.last_updated) @@ -345,36 +457,16 @@ def get_last_state_changes( entity_ids = [entity_id] if entity_id is not None else None - return _sorted_states_to_dict( - hass, - session, - reversed(states), - start_time, - entity_ids, - include_start_time_state=False, - ) - - -def get_states( - hass: HomeAssistant, - utc_point_in_time: datetime, - entity_ids: list[str] | None = None, - run: RecorderRuns | None = None, - filters: Any = None, - no_attributes: bool = False, -) -> list[LazyState]: - """Return the states at a specific point in time.""" - if ( - run is None - and (run := (recorder.run_information_from_instance(hass, utc_point_in_time))) - is None - ): - # History did not run before utc_point_in_time - return [] - - with session_scope(hass=hass) as session: - return _get_states_with_session( - hass, session, utc_point_in_time, entity_ids, run, filters, no_attributes + return cast( + MutableMapping[str, list[State]], + _sorted_states_to_dict( + hass, + session, + reversed(states), + start_time, + entity_ids, + include_start_time_state=False, + ), ) @@ -386,18 +478,17 @@ def _get_states_with_session( run: RecorderRuns | None = None, filters: Any | None = None, no_attributes: bool = False, -) -> list[LazyState]: +) -> list[State]: """Return the states at a specific point in time.""" if entity_ids and len(entity_ids) == 1: return _get_single_entity_states_with_session( hass, session, utc_point_in_time, entity_ids[0], no_attributes ) - if ( - run is None - and (run := (recorder.run_information_with_session(session, utc_point_in_time))) - is None - ): + if run is None: + run = recorder.get_instance(hass).run_history.get(utc_point_in_time) + + if run is None or process_timestamp(run.start) > utc_point_in_time: # History did not run before utc_point_in_time return [] @@ -482,7 +573,7 @@ def _get_single_entity_states_with_session( utc_point_in_time: datetime, entity_id: str, no_attributes: bool = False, -) -> list[LazyState]: +) -> list[State]: # Use an entirely different (and extremely fast) query if we only # have a single entity id baked_query, join_attributes = bake_query_and_join_attributes(hass, no_attributes) @@ -514,7 +605,7 @@ def _sorted_states_to_dict( include_start_time_state: bool = True, minimal_response: bool = False, no_attributes: bool = False, -) -> MutableMapping[str, Iterable[LazyState | State | dict[str, Any]]]: +) -> MutableMapping[str, list[State | dict[str, Any]]]: """Convert SQL results into JSON friendly data structure. This takes our state list and turns it into a JSON friendly data @@ -526,7 +617,7 @@ def _sorted_states_to_dict( each list of states, otherwise our graphs won't start on the Y axis correctly. """ - result: dict[str, list[LazyState | dict[str, Any]]] = defaultdict(list) + result: dict[str, list[State | dict[str, Any]]] = defaultdict(list) # Set all entity IDs to empty lists in result set to maintain the order if entity_ids is not None: for ent_id in entity_ids: @@ -535,13 +626,11 @@ def _sorted_states_to_dict( # Get the states at the start time timer_start = time.perf_counter() if include_start_time_state: - run = recorder.run_information_from_instance(hass, start_time) for state in _get_states_with_session( hass, session, start_time, entity_ids, - run=run, filters=filters, no_attributes=no_attributes, ): @@ -557,21 +646,30 @@ def _sorted_states_to_dict( # here _process_timestamp_to_utc_isoformat = process_timestamp_to_utc_isoformat + if entity_ids and len(entity_ids) == 1: + states_iter: Iterable[tuple[str | Column, Iterator[States]]] = ( + (entity_ids[0], iter(states)), + ) + else: + states_iter = groupby(states, lambda state: state.entity_id) + # Append all changes to it - for ent_id, group in groupby(states, lambda state: state.entity_id): # type: ignore[no-any-return] - domain = split_entity_id(ent_id)[0] + for ent_id, group in states_iter: ent_results = result[ent_id] attr_cache: dict[str, dict[str, Any]] = {} - if not minimal_response or domain in NEED_ATTRIBUTE_DOMAINS: + if not minimal_response or split_entity_id(ent_id)[0] in NEED_ATTRIBUTE_DOMAINS: ent_results.extend(LazyState(db_state, attr_cache) for db_state in group) + continue # With minimal response we only provide a native # State for the first and last response. All the states # in-between only provide the "state" and the # "last_changed". if not ent_results: - ent_results.append(LazyState(next(group), attr_cache)) + if (first_state := next(group, None)) is None: + continue + ent_results.append(LazyState(first_state, attr_cache)) prev_state = ent_results[-1] assert isinstance(prev_state, LazyState) @@ -601,15 +699,3 @@ def _sorted_states_to_dict( # Filter out the empty lists if some states had 0 results. return {key: val for key, val in result.items() if val} - - -def get_state( - hass: HomeAssistant, - utc_point_in_time: datetime, - entity_id: str, - run: RecorderRuns | None = None, - no_attributes: bool = False, -) -> LazyState | None: - """Return a state at a specific point in time.""" - states = get_states(hass, utc_point_in_time, [entity_id], run, None, no_attributes) - return states[0] if states else None diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index 62f740da174..0fb44f99ae2 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -2,7 +2,7 @@ "domain": "recorder", "name": "Recorder", "documentation": "https://www.home-assistant.io/integrations/recorder", - "requirements": ["sqlalchemy==1.4.32", "fnvhash==0.1.0", "lru-dict==1.1.7"], + "requirements": ["sqlalchemy==1.4.36", "fnvhash==0.1.0", "lru-dict==1.1.7"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal", "iot_class": "local_push" diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 87cfb88032f..94614fe8cff 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -56,7 +56,7 @@ def get_schema_version(instance: Any) -> int: current_version = getattr(res, "schema_version", None) if current_version is None: - current_version = _inspect_schema_version(instance.engine, session) + current_version = _inspect_schema_version(session) _LOGGER.debug( "No schema version found. Inspected version: %s", current_version ) @@ -646,12 +646,13 @@ def _apply_update(instance, new_version, old_version): # noqa: C901 big_int = "INTEGER(20)" if dialect == "mysql" else "INTEGER" _add_columns(instance, "states", [f"attributes_id {big_int}"]) _create_index(instance, "states", "ix_states_attributes_id") - + elif new_version == 26: + _create_index(instance, "statistics_runs", "ix_statistics_runs_start") else: raise ValueError(f"No schema migration defined for version {new_version}") -def _inspect_schema_version(engine, session): +def _inspect_schema_version(session): """Determine the schema version by inspecting the db structure. When the schema version is not present in the db, either db was just @@ -660,7 +661,7 @@ def _inspect_schema_version(engine, session): version 1 are present to make the determination. Eventually this logic can be removed and we can assume a new db is being created. """ - inspector = sqlalchemy.inspect(engine) + inspector = sqlalchemy.inspect(session.connection()) indexes = inspector.get_indexes("events") for index in indexes: diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 384bf4b22e2..5b402dec7a3 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -44,7 +44,7 @@ from .const import ALL_DOMAIN_EXCLUDE_ATTRS, JSON_DUMP # pylint: disable=invalid-name Base = declarative_base() -SCHEMA_VERSION = 25 +SCHEMA_VERSION = 26 _LOGGER = logging.getLogger(__name__) @@ -484,7 +484,7 @@ class StatisticsRuns(Base): # type: ignore[misc,valid-type] __tablename__ = TABLE_STATISTICS_RUNS run_id = Column(Integer, Identity(), primary_key=True) - start = Column(DateTime(timezone=True)) + start = Column(DateTime(timezone=True), index=True) def __repr__(self) -> str: """Return string representation of instance for debugging.""" @@ -619,7 +619,10 @@ class LazyState(State): def last_updated(self) -> datetime: # type: ignore[override] """Last updated datetime.""" if self._last_updated is None: - self._last_updated = process_timestamp(self._row.last_updated) + if (last_updated := self._row.last_updated) is not None: + self._last_updated = process_timestamp(last_updated) + else: + self._last_updated = self.last_changed return self._last_updated @last_updated.setter @@ -638,7 +641,10 @@ class LazyState(State): last_changed_isoformat = process_timestamp_to_utc_isoformat( self._row.last_changed ) - if self._row.last_changed == self._row.last_updated: + if ( + self._row.last_updated is None + or self._row.last_changed == self._row.last_updated + ): last_updated_isoformat = last_changed_isoformat else: last_updated_isoformat = process_timestamp_to_utc_isoformat( diff --git a/homeassistant/components/recorder/pool.py b/homeassistant/components/recorder/pool.py index 633c084ade4..027b9bfbc25 100644 --- a/homeassistant/components/recorder/pool.py +++ b/homeassistant/components/recorder/pool.py @@ -1,13 +1,22 @@ """A pool for sqlite connections.""" +import logging import threading +import traceback from typing import Any -from sqlalchemy.pool import NullPool, SingletonThreadPool +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.pool import NullPool, SingletonThreadPool, StaticPool from homeassistant.helpers.frame import report from .const import DB_WORKER_PREFIX +_LOGGER = logging.getLogger(__name__) + +# For debugging the MutexPool +DEBUG_MUTEX_POOL = True +DEBUG_MUTEX_POOL_TRACE = False + POOL_SIZE = 5 @@ -63,3 +72,55 @@ class RecorderPool(SingletonThreadPool, NullPool): # type: ignore[misc] return super( # pylint: disable=bad-super-call NullPool, self )._create_connection() + + +class MutexPool(StaticPool): # type: ignore[misc] + """A pool which prevents concurrent accesses from multiple threads. + + This is used in tests to prevent unsafe concurrent accesses to in-memory SQLite + databases. + """ + + _reference_counter = 0 + pool_lock: threading.RLock + + def _do_return_conn(self, conn: Any) -> None: + if DEBUG_MUTEX_POOL_TRACE: + trace = traceback.extract_stack() + trace_msg = "\n" + "".join(traceback.format_list(trace[:-1])) + else: + trace_msg = "" + + super()._do_return_conn(conn) + if DEBUG_MUTEX_POOL: + self._reference_counter -= 1 + _LOGGER.debug( + "%s return conn ctr: %s%s", + threading.current_thread().name, + self._reference_counter, + trace_msg, + ) + MutexPool.pool_lock.release() + + def _do_get(self) -> Any: + + if DEBUG_MUTEX_POOL_TRACE: + trace = traceback.extract_stack() + trace_msg = "".join(traceback.format_list(trace[:-1])) + else: + trace_msg = "" + + if DEBUG_MUTEX_POOL: + _LOGGER.debug("%s wait conn%s", threading.current_thread().name, trace_msg) + got_lock = MutexPool.pool_lock.acquire(timeout=1) + if not got_lock: + raise SQLAlchemyError + conn = super()._do_get() + if DEBUG_MUTEX_POOL: + self._reference_counter += 1 + _LOGGER.debug( + "%s get conn: ctr: %s", + threading.current_thread().name, + self._reference_counter, + ) + return conn diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 9bbe13ca5a7..d4061a69bab 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -1,14 +1,17 @@ """Purge old data helper.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Iterable from datetime import datetime +from itertools import zip_longest import logging from typing import TYPE_CHECKING -from sqlalchemy import func +from sqlalchemy import func, lambda_stmt, select, union_all from sqlalchemy.orm.session import Session from sqlalchemy.sql.expression import distinct +from sqlalchemy.sql.lambdas import StatementLambdaElement +from sqlalchemy.sql.selectable import Select from homeassistant.const import EVENT_STATE_CHANGED @@ -42,13 +45,15 @@ def purge_old_data( "Purging states and events before target %s", purge_before.isoformat(sep=" ", timespec="seconds"), ) + using_sqlite = instance.using_sqlite() with session_scope(session=instance.get_session()) as session: # type: ignore[misc] # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record - event_ids = _select_event_ids_to_purge(session, purge_before) - state_ids, attributes_ids = _select_state_and_attributes_ids_to_purge( - session, purge_before, event_ids - ) + ( + event_ids, + state_ids, + attributes_ids, + ) = _select_event_state_and_attributes_ids_to_purge(session, purge_before) statistics_runs = _select_statistics_runs_to_purge(session, purge_before) short_term_statistics = _select_short_term_statistics_to_purge( session, purge_before @@ -58,7 +63,7 @@ def purge_old_data( _purge_state_ids(instance, session, state_ids) if unused_attribute_ids_set := _select_unused_attributes_ids( - session, attributes_ids + session, attributes_ids, using_sqlite ): _purge_attributes_ids(instance, session, unused_attribute_ids_set) @@ -86,52 +91,308 @@ def purge_old_data( return True -def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list[int]: - """Return a list of event ids to purge.""" +def _select_event_state_and_attributes_ids_to_purge( + session: Session, purge_before: datetime +) -> tuple[set[int], set[int], set[int]]: + """Return a list of event, state, and attribute ids to purge.""" events = ( - session.query(Events.event_id) + session.query(Events.event_id, States.state_id, States.attributes_id) + .outerjoin(States, Events.event_id == States.event_id) .filter(Events.time_fired < purge_before) .limit(MAX_ROWS_TO_PURGE) .all() ) _LOGGER.debug("Selected %s event ids to remove", len(events)) - return [event.event_id for event in events] - - -def _select_state_and_attributes_ids_to_purge( - session: Session, purge_before: datetime, event_ids: list[int] -) -> tuple[set[int], set[int]]: - """Return a list of state ids to purge.""" - if not event_ids: - return set(), set() - states = ( - session.query(States.state_id, States.attributes_id) - .filter(States.last_updated < purge_before) - .filter(States.event_id.in_(event_ids)) - .all() - ) - _LOGGER.debug("Selected %s state ids to remove", len(states)) + event_ids = set() state_ids = set() attributes_ids = set() - for state in states: - state_ids.add(state.state_id) - if state.attributes_id: - attributes_ids.add(state.attributes_id) - return state_ids, attributes_ids + for event in events: + event_ids.add(event.event_id) + if event.state_id: + state_ids.add(event.state_id) + if event.attributes_id: + attributes_ids.add(event.attributes_id) + return event_ids, state_ids, attributes_ids + + +def _state_attrs_exist(attr: int | None) -> Select: + """Check if a state attributes id exists in the states table.""" + return select(func.min(States.attributes_id)).where(States.attributes_id == attr) + + +def _generate_find_attr_lambda( + attr1: int, + attr2: int | None, + attr3: int | None, + attr4: int | None, + attr5: int | None, + attr6: int | None, + attr7: int | None, + attr8: int | None, + attr9: int | None, + attr10: int | None, + attr11: int | None, + attr12: int | None, + attr13: int | None, + attr14: int | None, + attr15: int | None, + attr16: int | None, + attr17: int | None, + attr18: int | None, + attr19: int | None, + attr20: int | None, + attr21: int | None, + attr22: int | None, + attr23: int | None, + attr24: int | None, + attr25: int | None, + attr26: int | None, + attr27: int | None, + attr28: int | None, + attr29: int | None, + attr30: int | None, + attr31: int | None, + attr32: int | None, + attr33: int | None, + attr34: int | None, + attr35: int | None, + attr36: int | None, + attr37: int | None, + attr38: int | None, + attr39: int | None, + attr40: int | None, + attr41: int | None, + attr42: int | None, + attr43: int | None, + attr44: int | None, + attr45: int | None, + attr46: int | None, + attr47: int | None, + attr48: int | None, + attr49: int | None, + attr50: int | None, + attr51: int | None, + attr52: int | None, + attr53: int | None, + attr54: int | None, + attr55: int | None, + attr56: int | None, + attr57: int | None, + attr58: int | None, + attr59: int | None, + attr60: int | None, + attr61: int | None, + attr62: int | None, + attr63: int | None, + attr64: int | None, + attr65: int | None, + attr66: int | None, + attr67: int | None, + attr68: int | None, + attr69: int | None, + attr70: int | None, + attr71: int | None, + attr72: int | None, + attr73: int | None, + attr74: int | None, + attr75: int | None, + attr76: int | None, + attr77: int | None, + attr78: int | None, + attr79: int | None, + attr80: int | None, + attr81: int | None, + attr82: int | None, + attr83: int | None, + attr84: int | None, + attr85: int | None, + attr86: int | None, + attr87: int | None, + attr88: int | None, + attr89: int | None, + attr90: int | None, + attr91: int | None, + attr92: int | None, + attr93: int | None, + attr94: int | None, + attr95: int | None, + attr96: int | None, + attr97: int | None, + attr98: int | None, + attr99: int | None, + attr100: int | None, +) -> StatementLambdaElement: + """Generate the find attributes select only once. + + https://docs.sqlalchemy.org/en/14/core/connections.html#quick-guidelines-for-lambdas + """ + return lambda_stmt( + lambda: union_all( + _state_attrs_exist(attr1), + _state_attrs_exist(attr2), + _state_attrs_exist(attr3), + _state_attrs_exist(attr4), + _state_attrs_exist(attr5), + _state_attrs_exist(attr6), + _state_attrs_exist(attr7), + _state_attrs_exist(attr8), + _state_attrs_exist(attr9), + _state_attrs_exist(attr10), + _state_attrs_exist(attr11), + _state_attrs_exist(attr12), + _state_attrs_exist(attr13), + _state_attrs_exist(attr14), + _state_attrs_exist(attr15), + _state_attrs_exist(attr16), + _state_attrs_exist(attr17), + _state_attrs_exist(attr18), + _state_attrs_exist(attr19), + _state_attrs_exist(attr20), + _state_attrs_exist(attr21), + _state_attrs_exist(attr22), + _state_attrs_exist(attr23), + _state_attrs_exist(attr24), + _state_attrs_exist(attr25), + _state_attrs_exist(attr26), + _state_attrs_exist(attr27), + _state_attrs_exist(attr28), + _state_attrs_exist(attr29), + _state_attrs_exist(attr30), + _state_attrs_exist(attr31), + _state_attrs_exist(attr32), + _state_attrs_exist(attr33), + _state_attrs_exist(attr34), + _state_attrs_exist(attr35), + _state_attrs_exist(attr36), + _state_attrs_exist(attr37), + _state_attrs_exist(attr38), + _state_attrs_exist(attr39), + _state_attrs_exist(attr40), + _state_attrs_exist(attr41), + _state_attrs_exist(attr42), + _state_attrs_exist(attr43), + _state_attrs_exist(attr44), + _state_attrs_exist(attr45), + _state_attrs_exist(attr46), + _state_attrs_exist(attr47), + _state_attrs_exist(attr48), + _state_attrs_exist(attr49), + _state_attrs_exist(attr50), + _state_attrs_exist(attr51), + _state_attrs_exist(attr52), + _state_attrs_exist(attr53), + _state_attrs_exist(attr54), + _state_attrs_exist(attr55), + _state_attrs_exist(attr56), + _state_attrs_exist(attr57), + _state_attrs_exist(attr58), + _state_attrs_exist(attr59), + _state_attrs_exist(attr60), + _state_attrs_exist(attr61), + _state_attrs_exist(attr62), + _state_attrs_exist(attr63), + _state_attrs_exist(attr64), + _state_attrs_exist(attr65), + _state_attrs_exist(attr66), + _state_attrs_exist(attr67), + _state_attrs_exist(attr68), + _state_attrs_exist(attr69), + _state_attrs_exist(attr70), + _state_attrs_exist(attr71), + _state_attrs_exist(attr72), + _state_attrs_exist(attr73), + _state_attrs_exist(attr74), + _state_attrs_exist(attr75), + _state_attrs_exist(attr76), + _state_attrs_exist(attr77), + _state_attrs_exist(attr78), + _state_attrs_exist(attr79), + _state_attrs_exist(attr80), + _state_attrs_exist(attr81), + _state_attrs_exist(attr82), + _state_attrs_exist(attr83), + _state_attrs_exist(attr84), + _state_attrs_exist(attr85), + _state_attrs_exist(attr86), + _state_attrs_exist(attr87), + _state_attrs_exist(attr88), + _state_attrs_exist(attr89), + _state_attrs_exist(attr90), + _state_attrs_exist(attr91), + _state_attrs_exist(attr92), + _state_attrs_exist(attr93), + _state_attrs_exist(attr94), + _state_attrs_exist(attr95), + _state_attrs_exist(attr96), + _state_attrs_exist(attr97), + _state_attrs_exist(attr98), + _state_attrs_exist(attr99), + _state_attrs_exist(attr100), + ) + ) def _select_unused_attributes_ids( - session: Session, attributes_ids: set[int] + session: Session, attributes_ids: set[int], using_sqlite: bool ) -> set[int]: """Return a set of attributes ids that are not used by any states in the database.""" if not attributes_ids: return set() - to_remove = attributes_ids - { - state[0] - for state in session.query(distinct(States.attributes_id)) - .filter(States.attributes_id.in_(attributes_ids)) - .all() - } + + if using_sqlite: + # + # SQLite has a superior query optimizer for the distinct query below as it uses the + # covering index without having to examine the rows directly for both of the queries + # below. + # + # We use the distinct query for SQLite since the query in the other branch can + # generate more than 500 unions which SQLite does not support. + # + # How MariaDB's query optimizer handles this query: + # > explain select distinct attributes_id from states where attributes_id in (136723); + # ...Using index + # + seen_ids = { + state[0] + for state in session.query(distinct(States.attributes_id)) + .filter(States.attributes_id.in_(attributes_ids)) + .all() + } + else: + # + # This branch is for DBMS that cannot optimize the distinct query well and has to examine + # all the rows that match. + # + # This branch uses a union of simple queries, as each query is optimized away as the answer + # to the query can be found in the index. + # + # The below query works for SQLite as long as there are no more than 500 attributes_id + # to be selected. We currently do not have MySQL or PostgreSQL servers running in the + # test suite; we test this path using SQLite when there are less than 500 attributes_id. + # + # How MariaDB's query optimizer handles this query: + # > explain select min(attributes_id) from states where attributes_id = 136723; + # ...Select tables optimized away + # + # We used to generate a query based on how many attribute_ids to find but + # that meant sqlalchemy Transparent SQL Compilation Caching was working against + # us by cached up to MAX_ROWS_TO_PURGE different statements which could be + # up to 500MB for large database due to the complexity of the ORM objects. + # + # We now break the query into groups of 100 and use a lambda_stmt to ensure + # that the query is only cached once. + # + seen_ids = set() + groups = [iter(attributes_ids)] * 100 + for attr_ids in zip_longest(*groups, fillvalue=None): + seen_ids |= { + state[0] + for state in session.execute( + _generate_find_attr_lambda(*attr_ids) + ).all() + if state[0] is not None + } + to_remove = attributes_ids - seen_ids _LOGGER.debug( "Selected %s shared attributes to remove", len(to_remove), @@ -276,7 +537,7 @@ def _purge_short_term_statistics( _LOGGER.debug("Deleted %s short term statistics", deleted_rows) -def _purge_event_ids(session: Session, event_ids: list[int]) -> None: +def _purge_event_ids(session: Session, event_ids: Iterable[int]) -> None: """Delete by event id.""" deleted_rows = ( session.query(Events) @@ -291,11 +552,10 @@ def _purge_old_recorder_runs( ) -> None: """Purge all old recorder runs.""" # Recorder runs is small, no need to batch run it - assert instance.run_info is not None deleted_rows = ( session.query(RecorderRuns) .filter(RecorderRuns.start < purge_before) - .filter(RecorderRuns.run_id != instance.run_info.run_id) + .filter(RecorderRuns.run_id != instance.run_history.current.run_id) .delete(synchronize_session=False) ) _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) @@ -304,6 +564,7 @@ def _purge_old_recorder_runs( def _purge_filtered_data(instance: Recorder, session: Session) -> bool: """Remove filtered states and events that shouldn't be in the database.""" _LOGGER.debug("Cleanup filtered data") + using_sqlite = instance.using_sqlite() # Check if excluded entity_ids are in database excluded_entity_ids: list[str] = [ @@ -312,7 +573,7 @@ def _purge_filtered_data(instance: Recorder, session: Session) -> bool: if not instance.entity_filter(entity_id) ] if len(excluded_entity_ids) > 0: - _purge_filtered_states(instance, session, excluded_entity_ids) + _purge_filtered_states(instance, session, excluded_entity_ids, using_sqlite) return False # Check if excluded event_types are in database @@ -329,12 +590,15 @@ def _purge_filtered_data(instance: Recorder, session: Session) -> bool: def _purge_filtered_states( - instance: Recorder, session: Session, excluded_entity_ids: list[str] + instance: Recorder, + session: Session, + excluded_entity_ids: list[str], + using_sqlite: bool, ) -> None: """Remove filtered states and linked events.""" state_ids: list[int] attributes_ids: list[int] - event_ids: list[int | None] + event_ids: list[int] state_ids, attributes_ids, event_ids = zip( *( session.query(States.state_id, States.attributes_id, States.event_id) @@ -348,9 +612,9 @@ def _purge_filtered_states( "Selected %s state_ids to remove that should be filtered", len(state_ids) ) _purge_state_ids(instance, session, set(state_ids)) - _purge_event_ids(session, event_ids) # type: ignore[arg-type] # type of event_ids already narrowed to 'list[int]' + _purge_event_ids(session, event_ids) unused_attribute_ids_set = _select_unused_attributes_ids( - session, {id_ for id_ in attributes_ids if id_ is not None} + session, {id_ for id_ in attributes_ids if id_ is not None}, using_sqlite ) _purge_attributes_ids(instance, session, unused_attribute_ids_set) @@ -385,6 +649,7 @@ def _purge_filtered_events( @retryable_database_job("purge") def purge_entity_data(instance: Recorder, entity_filter: Callable[[str], bool]) -> bool: """Purge states and events of specified entities.""" + using_sqlite = instance.using_sqlite() with session_scope(session=instance.get_session()) as session: # type: ignore[misc] selected_entity_ids: list[str] = [ entity_id @@ -394,7 +659,7 @@ def purge_entity_data(instance: Recorder, entity_filter: Callable[[str], bool]) _LOGGER.debug("Purging entity data for %s", selected_entity_ids) if len(selected_entity_ids) > 0: # Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record - _purge_filtered_states(instance, session, selected_entity_ids) + _purge_filtered_states(instance, session, selected_entity_ids, using_sqlite) _LOGGER.debug("Purging entity data hasn't fully completed yet") return False diff --git a/homeassistant/components/recorder/repack.py b/homeassistant/components/recorder/repack.py index 95df0681ddb..c272f2827a0 100644 --- a/homeassistant/components/recorder/repack.py +++ b/homeassistant/components/recorder/repack.py @@ -4,6 +4,8 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from sqlalchemy import text + if TYPE_CHECKING: from . import Recorder @@ -18,7 +20,9 @@ def repack_database(instance: Recorder) -> None: # Execute sqlite command to free up space on disk if dialect_name == "sqlite": _LOGGER.debug("Vacuuming SQL DB to free space") - instance.engine.execute("VACUUM") + with instance.engine.connect() as conn: + conn.execute(text("VACUUM")) + conn.commit() return # Execute postgresql vacuum command to free up space on disk @@ -27,11 +31,14 @@ def repack_database(instance: Recorder) -> None: with instance.engine.connect().execution_options( isolation_level="AUTOCOMMIT" ) as conn: - conn.execute("VACUUM") + conn.execute(text("VACUUM")) + conn.commit() return # Optimize mysql / mariadb tables to free up space on disk if dialect_name == "mysql": _LOGGER.debug("Optimizing SQL DB to free space") - instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") + with instance.engine.connect() as conn: + conn.execute(text("OPTIMIZE TABLE states, events, recorder_runs")) + conn.commit() return diff --git a/homeassistant/components/recorder/run_history.py b/homeassistant/components/recorder/run_history.py new file mode 100644 index 00000000000..3a76eef3c83 --- /dev/null +++ b/homeassistant/components/recorder/run_history.py @@ -0,0 +1,133 @@ +"""Track recorder run history.""" +from __future__ import annotations + +import bisect +from dataclasses import dataclass +from datetime import datetime + +from sqlalchemy.orm.session import Session + +import homeassistant.util.dt as dt_util + +from .models import RecorderRuns, process_timestamp + + +def _find_recorder_run_for_start_time( + run_history: _RecorderRunsHistory, start: datetime +) -> RecorderRuns | None: + """Find the recorder run for a start time in _RecorderRunsHistory.""" + run_timestamps = run_history.run_timestamps + runs_by_timestamp = run_history.runs_by_timestamp + + # bisect_left tells us were we would insert + # a value in the list of runs after the start timestamp. + # + # The run before that (idx-1) is when the run started + # + # If idx is 0, history never ran before the start timestamp + # + if idx := bisect.bisect_left(run_timestamps, start.timestamp()): + return runs_by_timestamp[run_timestamps[idx - 1]] + return None + + +@dataclass(frozen=True) +class _RecorderRunsHistory: + """Bisectable history of RecorderRuns.""" + + run_timestamps: list[int] + runs_by_timestamp: dict[int, RecorderRuns] + + +class RunHistory: + """Track recorder run history.""" + + def __init__(self) -> None: + """Track recorder run history.""" + self._recording_start = dt_util.utcnow() + self._current_run_info: RecorderRuns | None = None + self._run_history = _RecorderRunsHistory([], {}) + + @property + def recording_start(self) -> datetime: + """Return the time the recorder started recording states.""" + return self._recording_start + + @property + def current(self) -> RecorderRuns: + """Get the current run.""" + assert self._current_run_info is not None + return self._current_run_info + + def get(self, start: datetime) -> RecorderRuns | None: + """Return the recorder run that started before or at start. + + If the first run started after the start, return None + """ + if start >= self.recording_start: + return self.current + return _find_recorder_run_for_start_time(self._run_history, start) + + def start(self, session: Session) -> None: + """Start a new run. + + Must run in the recorder thread. + """ + self._current_run_info = RecorderRuns( + start=self.recording_start, created=dt_util.utcnow() + ) + session.add(self._current_run_info) + session.flush() + session.expunge(self._current_run_info) + self.load_from_db(session) + + def reset(self) -> None: + """Reset the run when the database is changed or fails. + + Must run in the recorder thread. + """ + self._recording_start = dt_util.utcnow() + self._current_run_info = None + + def end(self, session: Session) -> None: + """End the current run. + + Must run in the recorder thread. + """ + assert self._current_run_info is not None + self._current_run_info.end = dt_util.utcnow() + session.add(self._current_run_info) + + def load_from_db(self, session: Session) -> None: + """Update the run cache. + + Must run in the recorder thread. + """ + run_timestamps: list[int] = [] + runs_by_timestamp: dict[int, RecorderRuns] = {} + + for run in session.query(RecorderRuns).order_by(RecorderRuns.start.asc()).all(): + session.expunge(run) + if run_dt := process_timestamp(run.start): + timestamp = run_dt.timestamp() + run_timestamps.append(timestamp) + runs_by_timestamp[timestamp] = run + + # + # self._run_history is accessed in get() + # which is allowed to be called from any thread + # + # We use a dataclass to ensure that when we update + # run_timestamps and runs_by_timestamp + # are never out of sync with each other. + # + self._run_history = _RecorderRunsHistory(run_timestamps, runs_by_timestamp) + + def clear(self) -> None: + """Clear the current run after ending it. + + Must run in the recorder thread. + """ + assert self._current_run_info is not None + assert self._current_run_info.end is not None + self._current_run_info = None diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index bf4108a87c1..1c993b32bb6 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Any, Literal, overload from sqlalchemy import bindparam, func from sqlalchemy.exc import SQLAlchemyError, StatementError from sqlalchemy.ext import baked -from sqlalchemy.orm.scoping import scoped_session +from sqlalchemy.orm.session import Session from sqlalchemy.sql.expression import literal_column, true import voluptuous as vol @@ -121,8 +121,6 @@ QUERY_STATISTIC_META_ID = [ ] STATISTICS_BAKERY = "recorder_statistics_bakery" -STATISTICS_META_BAKERY = "recorder_statistics_meta_bakery" -STATISTICS_SHORT_TERM_BAKERY = "recorder_statistics_short_term_bakery" # Convert pressure, temperature and volume statistics from the normalized unit used for @@ -159,6 +157,14 @@ DISPLAY_UNIT_TO_STATISTIC_UNIT_CONVERSIONS: dict[ _LOGGER = logging.getLogger(__name__) +@dataclasses.dataclass +class PlatformCompiledStatistics: + """Compiled Statistics from a platform.""" + + platform_stats: list[StatisticResult] + current_metadata: dict[str, tuple[int, StatisticMetaData]] + + def split_statistic_id(entity_id: str) -> list[str]: """Split a state entity ID into domain and object ID.""" return entity_id.split(":", 1) @@ -198,8 +204,6 @@ class ValidationIssue: def async_setup(hass: HomeAssistant) -> None: """Set up the history hooks.""" hass.data[STATISTICS_BAKERY] = baked.bakery() - hass.data[STATISTICS_META_BAKERY] = baked.bakery() - hass.data[STATISTICS_SHORT_TERM_BAKERY] = baked.bakery() def _entity_id_changed(event: Event) -> None: """Handle entity_id changed.""" @@ -240,9 +244,9 @@ def get_start_time() -> datetime: def _update_or_add_metadata( - hass: HomeAssistant, - session: scoped_session, + session: Session, new_metadata: StatisticMetaData, + old_metadata_dict: dict[str, tuple[int, StatisticMetaData]], ) -> int: """Get metadata_id for a statistic_id. @@ -252,10 +256,7 @@ def _update_or_add_metadata( Updating metadata source is not possible. """ statistic_id = new_metadata["statistic_id"] - old_metadata_dict = get_metadata_with_session( - hass, session, statistic_ids=[statistic_id] - ) - if not old_metadata_dict: + if statistic_id not in old_metadata_dict: meta = StatisticsMeta.from_meta(new_metadata) session.add(meta) session.flush() # Flush to get the metadata id assigned @@ -291,7 +292,7 @@ def _update_or_add_metadata( def _find_duplicates( - session: scoped_session, table: type[Statistics | StatisticsShortTerm] + session: Session, table: type[Statistics | StatisticsShortTerm] ) -> tuple[list[int], list[dict]]: """Find duplicated statistics.""" subquery = ( @@ -356,7 +357,7 @@ def _find_duplicates( def _delete_duplicates_from_table( - session: scoped_session, table: type[Statistics | StatisticsShortTerm] + session: Session, table: type[Statistics | StatisticsShortTerm] ) -> tuple[int, list[dict]]: """Identify and delete duplicated statistics from a specified table.""" all_non_identical_duplicates: list[dict] = [] @@ -376,7 +377,7 @@ def _delete_duplicates_from_table( return (total_deleted_rows, all_non_identical_duplicates) -def delete_duplicates(instance: Recorder, session: scoped_session) -> None: +def delete_duplicates(instance: Recorder, session: Session) -> None: """Identify and delete duplicated statistics. A backup will be made of duplicated statistics before it is deleted. @@ -420,7 +421,7 @@ def delete_duplicates(instance: Recorder, session: scoped_session) -> None: def compile_hourly_statistics( - instance: Recorder, session: scoped_session, start: datetime + instance: Recorder, session: Session, start: datetime ) -> None: """Compile hourly statistics. @@ -433,7 +434,7 @@ def compile_hourly_statistics( # Compute last hour's average, min, max summary: dict[str, StatisticData] = {} - baked_query = instance.hass.data[STATISTICS_SHORT_TERM_BAKERY]( + baked_query = instance.hass.data[STATISTICS_BAKERY]( lambda session: session.query(*QUERY_STATISTICS_SUMMARY_MEAN) ) @@ -492,7 +493,7 @@ def compile_hourly_statistics( "sum": _sum, } else: - baked_query = instance.hass.data[STATISTICS_SHORT_TERM_BAKERY]( + baked_query = instance.hass.data[STATISTICS_BAKERY]( lambda session: session.query(*QUERY_STATISTICS_SUMMARY_SUM_LEGACY) ) @@ -557,15 +558,23 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: _LOGGER.debug("Compiling statistics for %s-%s", start, end) platform_stats: list[StatisticResult] = [] + current_metadata: dict[str, tuple[int, StatisticMetaData]] = {} # Collect statistics from all platforms implementing support for domain, platform in instance.hass.data[DOMAIN].items(): if not hasattr(platform, "compile_statistics"): continue - platform_stat = platform.compile_statistics(instance.hass, start, end) - _LOGGER.debug( - "Statistics for %s during %s-%s: %s", domain, start, end, platform_stat + compiled: PlatformCompiledStatistics = platform.compile_statistics( + instance.hass, start, end ) - platform_stats.extend(platform_stat) + _LOGGER.debug( + "Statistics for %s during %s-%s: %s", + domain, + start, + end, + compiled.platform_stats, + ) + platform_stats.extend(compiled.platform_stats) + current_metadata.update(compiled.current_metadata) # Insert collected statistics in the database with session_scope( @@ -573,7 +582,9 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: exception_filter=_filter_unique_constraint_integrity_error(instance), ) as session: for stats in platform_stats: - metadata_id = _update_or_add_metadata(instance.hass, session, stats["meta"]) + metadata_id = _update_or_add_metadata( + session, stats["meta"], current_metadata + ) _insert_statistics( session, StatisticsShortTerm, @@ -591,7 +602,7 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool: def _adjust_sum_statistics( - session: scoped_session, + session: Session, table: type[Statistics | StatisticsShortTerm], metadata_id: int, start_time: datetime, @@ -615,7 +626,7 @@ def _adjust_sum_statistics( def _insert_statistics( - session: scoped_session, + session: Session, table: type[Statistics | StatisticsShortTerm], metadata_id: int, statistic: StatisticData, @@ -632,7 +643,7 @@ def _insert_statistics( def _update_statistics( - session: scoped_session, + session: Session, table: type[Statistics | StatisticsShortTerm], stat_id: int, statistic: StatisticData, @@ -660,7 +671,7 @@ def _update_statistics( def get_metadata_with_session( hass: HomeAssistant, - session: scoped_session, + session: Session, *, statistic_ids: list[str] | tuple[str] | None = None, statistic_type: Literal["mean"] | Literal["sum"] | None = None, @@ -675,7 +686,7 @@ def get_metadata_with_session( """ # Fetch metatadata from the database - baked_query = hass.data[STATISTICS_META_BAKERY]( + baked_query = hass.data[STATISTICS_BAKERY]( lambda session: session.query(*QUERY_STATISTIC_META) ) if statistic_ids is not None: @@ -845,16 +856,13 @@ def _statistics_during_period_query( hass: HomeAssistant, end_time: datetime | None, statistic_ids: list[str] | None, - bakery: Any, - base_query: Iterable, + baked_query: baked.BakedQuery, table: type[Statistics | StatisticsShortTerm], ) -> Callable: """Prepare a database query for statistics during a given period. This prepares a baked query, so we don't insert the parameters yet. """ - baked_query = hass.data[bakery](lambda session: session.query(*base_query)) - baked_query += lambda q: q.filter(table.start >= bindparam("start_time")) if end_time is not None: @@ -991,17 +999,18 @@ def statistics_during_period( if statistic_ids is not None: metadata_ids = [metadata_id for metadata_id, _ in metadata.values()] + bakery = hass.data[STATISTICS_BAKERY] if period == "5minute": - bakery = STATISTICS_SHORT_TERM_BAKERY - base_query = QUERY_STATISTICS_SHORT_TERM + baked_query = bakery( + lambda session: session.query(*QUERY_STATISTICS_SHORT_TERM) + ) table = StatisticsShortTerm else: - bakery = STATISTICS_BAKERY - base_query = QUERY_STATISTICS + baked_query = bakery(lambda session: session.query(*QUERY_STATISTICS)) table = Statistics baked_query = _statistics_during_period_query( - hass, end_time, statistic_ids, bakery, base_query, table + hass, end_time, statistic_ids, baked_query, table ) stats = execute( @@ -1050,14 +1059,13 @@ def _get_last_statistics( if not metadata: return {} + bakery = hass.data[STATISTICS_BAKERY] if table == StatisticsShortTerm: - bakery = STATISTICS_SHORT_TERM_BAKERY - base_query = QUERY_STATISTICS_SHORT_TERM + baked_query = bakery( + lambda session: session.query(*QUERY_STATISTICS_SHORT_TERM) + ) else: - bakery = STATISTICS_BAKERY - base_query = QUERY_STATISTICS - - baked_query = hass.data[bakery](lambda session: session.query(*base_query)) + baked_query = bakery(lambda session: session.query(*QUERY_STATISTICS)) baked_query += lambda q: q.filter_by(metadata_id=bindparam("metadata_id")) metadata_id = metadata[statistic_id][0] @@ -1105,8 +1113,63 @@ def get_last_short_term_statistics( ) +def get_latest_short_term_statistics( + hass: HomeAssistant, + statistic_ids: list[str], + metadata: dict[str, tuple[int, StatisticMetaData]] | None = None, +) -> dict[str, list[dict]]: + """Return the latest short term statistics for a list of statistic_ids.""" + # This function doesn't use a baked query, we instead rely on the + # "Transparent SQL Compilation Caching" feature introduced in SQLAlchemy 1.4 + with session_scope(hass=hass) as session: + # Fetch metadata for the given statistic_ids + if not metadata: + metadata = get_metadata_with_session( + hass, session, statistic_ids=statistic_ids + ) + if not metadata: + return {} + metadata_ids = [ + metadata[statistic_id][0] + for statistic_id in statistic_ids + if statistic_id in metadata + ] + most_recent_statistic_row = ( + session.query( + StatisticsShortTerm.metadata_id, + func.max(StatisticsShortTerm.start).label("start_max"), + ) + .filter(StatisticsShortTerm.metadata_id.in_(metadata_ids)) + .group_by(StatisticsShortTerm.metadata_id) + ).subquery() + stats = execute( + session.query(*QUERY_STATISTICS_SHORT_TERM).join( + most_recent_statistic_row, + ( + StatisticsShortTerm.metadata_id # pylint: disable=comparison-with-callable + == most_recent_statistic_row.c.metadata_id + ) + & (StatisticsShortTerm.start == most_recent_statistic_row.c.start_max), + ) + ) + if not stats: + return {} + + # Return statistics combined with metadata + return _sorted_statistics_to_dict( + hass, + session, + stats, + statistic_ids, + metadata, + False, + StatisticsShortTerm, + None, + ) + + def _statistics_at_time( - session: scoped_session, + session: Session, metadata_ids: set[int], table: type[Statistics | StatisticsShortTerm], start_time: datetime, @@ -1139,7 +1202,7 @@ def _statistics_at_time( def _sorted_statistics_to_dict( hass: HomeAssistant, - session: scoped_session, + session: Session, stats: list, statistic_ids: list[str] | None, _metadata: dict[str, tuple[int, StatisticMetaData]], @@ -1222,7 +1285,7 @@ def validate_statistics(hass: HomeAssistant) -> dict[str, list[ValidationIssue]] def _statistics_exists( - session: scoped_session, + session: Session, table: type[Statistics | StatisticsShortTerm], metadata_id: int, start: datetime, @@ -1316,7 +1379,10 @@ def add_external_statistics( session=instance.get_session(), # type: ignore[misc] exception_filter=_filter_unique_constraint_integrity_error(instance), ) as session: - metadata_id = _update_or_add_metadata(instance.hass, session, metadata) + old_metadata_dict = get_metadata_with_session( + instance.hass, session, statistic_ids=[metadata["statistic_id"]] + ) + metadata_id = _update_or_add_metadata(session, metadata, old_metadata_dict) for stat in statistics: if stat_id := _statistics_exists( session, Statistics, metadata_id, stat["start"] diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index 487b8dd22f7..b67f4c6d558 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Callable, Generator from contextlib import contextmanager -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta import functools import logging import os @@ -65,6 +65,10 @@ RETRYABLE_MYSQL_ERRORS = (1205, 1206, 1213) # 1206: The total number of locks exceeds the lock table size # 1213: Deadlock found when trying to get lock; try restarting transaction +FIRST_POSSIBLE_SUNDAY = 8 +SUNDAY_WEEKDAY = 6 +DAYS_IN_WEEK = 7 + @contextmanager def session_scope( @@ -460,8 +464,8 @@ def retryable_database_job(description: str) -> Callable: return decorator -def perodic_db_cleanups(instance: Recorder) -> None: - """Run any database cleanups that need to happen perodiclly. +def periodic_db_cleanups(instance: Recorder) -> None: + """Run any database cleanups that need to happen periodically. These cleanups will happen nightly or after any purge. """ @@ -501,3 +505,19 @@ def async_migration_in_progress(hass: HomeAssistant) -> bool: return False instance: Recorder = hass.data[DATA_INSTANCE] return instance.migration_in_progress + + +def second_sunday(year: int, month: int) -> date: + """Return the datetime.date for the second sunday of a month.""" + second = date(year, month, FIRST_POSSIBLE_SUNDAY) + day_of_week = second.weekday() + if day_of_week == SUNDAY_WEEKDAY: + return second + return second.replace( + day=(FIRST_POSSIBLE_SUNDAY + (SUNDAY_WEEKDAY - day_of_week) % DAYS_IN_WEEK) + ) + + +def is_second_sunday(date_time: datetime) -> bool: + """Check if a time is the second sunday of the month.""" + return bool(second_sunday(date_time.year, date_time.month).day == date_time.day) diff --git a/homeassistant/components/reddit/manifest.json b/homeassistant/components/reddit/manifest.json index f641bfd7a57..4c3d2bff7f9 100644 --- a/homeassistant/components/reddit/manifest.json +++ b/homeassistant/components/reddit/manifest.json @@ -2,7 +2,7 @@ "domain": "reddit", "name": "Reddit", "documentation": "https://www.home-assistant.io/integrations/reddit", - "requirements": ["praw==7.4.0"], + "requirements": ["praw==7.5.0"], "codeowners": [], "iot_class": "cloud_polling", "loggers": ["praw", "prawcore"] diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 8b06827f0d9..1a739a2a476 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations from collections.abc import Iterable from dataclasses import dataclass from datetime import timedelta +from enum import IntEnum import functools as ft import logging from typing import Any, cast, final @@ -61,6 +62,17 @@ DEFAULT_NUM_REPEATS = 1 DEFAULT_DELAY_SECS = 0.4 DEFAULT_HOLD_SECS = 0 + +class RemoteEntityFeature(IntEnum): + """Supported features of the remote entity.""" + + LEARN_COMMAND = 1 + DELETE_COMMAND = 2 + ACTIVITY = 4 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the RemoteEntityFeature enum instead. SUPPORT_LEARN_COMMAND = 1 SUPPORT_DELETE_COMMAND = 2 SUPPORT_ACTIVITY = 4 @@ -175,7 +187,7 @@ class RemoteEntity(ToggleEntity): @property def state_attributes(self) -> dict[str, Any] | None: """Return optional state attributes.""" - if not self.supported_features & SUPPORT_ACTIVITY: + if not self.supported_features & RemoteEntityFeature.ACTIVITY: return None return { diff --git a/homeassistant/components/remote/translations/hu.json b/homeassistant/components/remote/translations/hu.json index 2291d4f8b94..52b24f79f08 100644 --- a/homeassistant/components/remote/translations/hu.json +++ b/homeassistant/components/remote/translations/hu.json @@ -10,6 +10,7 @@ "is_on": "{entity_name} be van kapcsolva" }, "trigger_type": { + "changed_states": "{entity_name} be- vagy kikapcsolt", "toggled": "{entity_name} \u00e1tkapcsolt", "turned_off": "{entity_name} ki lett kapcsolva", "turned_on": "{entity_name} be lett kapcsolva" diff --git a/homeassistant/components/renault/diagnostics.py b/homeassistant/components/renault/diagnostics.py index f8b0a0d926e..2029ef989d6 100644 --- a/homeassistant/components/renault/diagnostics.py +++ b/homeassistant/components/renault/diagnostics.py @@ -59,7 +59,9 @@ def _get_vehicle_diagnostics(vehicle: RenaultVehicleProxy) -> dict[str, Any]: return { "details": async_redact_data(vehicle.details.raw_data, TO_REDACT), "data": { - key: async_redact_data(coordinator.data.raw_data, TO_REDACT) + key: async_redact_data( + coordinator.data.raw_data if coordinator.data else None, TO_REDACT + ) for key, coordinator in vehicle.coordinators.items() }, } diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index e07023cbbcd..e1ebf24c63b 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_TYPE @@ -173,10 +173,15 @@ async def async_setup_platform( class RflinkLight(SwitchableRflinkDevice, LightEntity): """Representation of a Rflink light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity): """Rflink light device that support dimming.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} _brightness = 255 async def async_added_to_hass(self): @@ -221,13 +226,8 @@ class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - -class HybridRflinkLight(DimmableRflinkLight, LightEntity): +class HybridRflinkLight(DimmableRflinkLight): """Rflink light device that sends out both dim and on/off commands. Used for protocols which support lights that are not exclusively on/off @@ -251,7 +251,7 @@ class HybridRflinkLight(DimmableRflinkLight, LightEntity): await self._async_handle_command("turn_on") -class ToggleRflinkLight(SwitchableRflinkDevice, LightEntity): +class ToggleRflinkLight(RflinkLight): """Rflink light device which sends out only 'on' commands. Some switches like for example Livolo light switches use the diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index d350998e2a9..ceb34520b07 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -5,15 +5,7 @@ import logging import RFXtrx as rfxtrxmod -from homeassistant.components.cover import ( - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_STOP, - SUPPORT_STOP_TILT, - CoverEntity, -) +from homeassistant.components.cover import CoverEntity, CoverEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OPEN from homeassistant.core import HomeAssistant, callback @@ -91,14 +83,18 @@ class RfxtrxCover(RfxtrxCommandEntity, CoverEntity): @property def supported_features(self): """Flag supported features.""" - supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + supported_features = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP + ) if self._venetian_blind_mode in ( CONST_VENETIAN_BLIND_MODE_US, CONST_VENETIAN_BLIND_MODE_EU, ): supported_features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT ) return supported_features diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index 7212b65cd7e..87f8c68aaac 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -5,11 +5,7 @@ import logging import RFXtrx as rfxtrxmod -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant, callback @@ -20,8 +16,6 @@ from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST _LOGGER = logging.getLogger(__name__) -SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS - def supported(event: rfxtrxmod.RFXtrxEvent): """Return whether an event supports light.""" @@ -60,6 +54,8 @@ async def async_setup_entry( class RfxtrxLight(RfxtrxCommandEntity, LightEntity): """Representation of a RFXtrx light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} _brightness = 0 _device: rfxtrxmod.LightingDevice @@ -78,11 +74,6 @@ class RfxtrxLight(RfxtrxCommandEntity, LightEntity): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_RFXTRX - @property def is_on(self): """Return true if device is on.""" diff --git a/homeassistant/components/rfxtrx/siren.py b/homeassistant/components/rfxtrx/siren.py index 9a4a475998d..282933f7f85 100644 --- a/homeassistant/components/rfxtrx/siren.py +++ b/homeassistant/components/rfxtrx/siren.py @@ -5,12 +5,7 @@ from typing import Any import RFXtrx as rfxtrxmod -from homeassistant.components.siren import ( - SUPPORT_TONES, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SirenEntity, -) +from homeassistant.components.siren import SirenEntity, SirenEntityFeature from homeassistant.components.siren.const import ATTR_TONE from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback @@ -26,8 +21,6 @@ from . import ( ) from .const import CONF_OFF_DELAY -SUPPORT_RFXTRX = SUPPORT_TURN_ON | SUPPORT_TONES - SECURITY_PANIC_ON = "Panic" SECURITY_PANIC_OFF = "End Panic" SECURITY_PANIC_ALL = {SECURITY_PANIC_ON, SECURITY_PANIC_OFF} @@ -129,13 +122,13 @@ class RfxtrxOffDelayMixin(Entity): class RfxtrxChime(RfxtrxCommandEntity, SirenEntity, RfxtrxOffDelayMixin): """Representation of a RFXtrx chime.""" + _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TONES _device: rfxtrxmod.ChimeDevice def __init__(self, device, device_id, off_delay=None, event=None): """Initialize the entity.""" super().__init__(device, device_id, event) self._attr_available_tones = list(self._device.COMMANDS.values()) - self._attr_supported_features = SUPPORT_TURN_ON | SUPPORT_TONES self._default_tone = next(iter(self._device.COMMANDS)) self._off_delay = off_delay @@ -180,12 +173,12 @@ class RfxtrxChime(RfxtrxCommandEntity, SirenEntity, RfxtrxOffDelayMixin): class RfxtrxSecurityPanic(RfxtrxCommandEntity, SirenEntity, RfxtrxOffDelayMixin): """Representation of a security device.""" + _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF _device: rfxtrxmod.SecurityDevice def __init__(self, device, device_id, off_delay=None, event=None): """Initialize the entity.""" super().__init__(device, device_id, event) - self._attr_supported_features = SUPPORT_TURN_ON | SUPPORT_TURN_OFF self._on_value = get_first_key(self._device.STATUS, SECURITY_PANIC_ON) self._off_value = get_first_key(self._device.STATUS, SECURITY_PANIC_OFF) self._off_delay = off_delay diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index aa76d89fea1..5b2cd1c54db 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -4,7 +4,7 @@ import logging import requests -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -47,6 +47,9 @@ async def async_setup_entry( class RingLight(RingEntityMixin, LightEntity): """Creates a switch to turn the ring cameras light on and off.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__(self, config_entry_id, device): """Initialize the light.""" super().__init__(config_entry_id, device) diff --git a/homeassistant/components/risco/alarm_control_panel.py b/homeassistant/components/risco/alarm_control_panel.py index edb72f66fe1..f3578151acc 100644 --- a/homeassistant/components/risco/alarm_control_panel.py +++ b/homeassistant/components/risco/alarm_control_panel.py @@ -2,14 +2,9 @@ import logging from homeassistant.components.alarm_control_panel import ( - FORMAT_NUMBER, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_CUSTOM_BYPASS, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -42,10 +37,10 @@ from .entity import RiscoEntity _LOGGER = logging.getLogger(__name__) STATES_TO_SUPPORTED_FEATURES = { - STATE_ALARM_ARMED_AWAY: SUPPORT_ALARM_ARM_AWAY, - STATE_ALARM_ARMED_CUSTOM_BYPASS: SUPPORT_ALARM_ARM_CUSTOM_BYPASS, - STATE_ALARM_ARMED_HOME: SUPPORT_ALARM_ARM_HOME, - STATE_ALARM_ARMED_NIGHT: SUPPORT_ALARM_ARM_NIGHT, + STATE_ALARM_ARMED_AWAY: AlarmControlPanelEntityFeature.ARM_AWAY, + STATE_ALARM_ARMED_CUSTOM_BYPASS: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME: AlarmControlPanelEntityFeature.ARM_HOME, + STATE_ALARM_ARMED_NIGHT: AlarmControlPanelEntityFeature.ARM_NIGHT, } @@ -137,7 +132,7 @@ class RiscoAlarm(AlarmControlPanelEntity, RiscoEntity): @property def code_format(self): """Return one or more digits/characters.""" - return FORMAT_NUMBER + return CodeFormat.NUMBER def _validate_code(self, code): """Validate given code.""" diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index f7e88ad1ed1..e6fe0d7dcf5 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -15,6 +15,7 @@ from homeassistant.components.media_player import ( BrowseMedia, MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, async_process_play_media_url, ) from homeassistant.components.media_player.const import ( @@ -24,17 +25,6 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_URL, MEDIA_TYPE_VIDEO, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, ) from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, HLS_PROVIDER from homeassistant.config_entries import ConfigEntry @@ -68,20 +58,6 @@ from .helpers import format_channel_name, roku_exception_handler _LOGGER = logging.getLogger(__name__) -SUPPORT_ROKU = ( - SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_SELECT_SOURCE - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_BROWSE_MEDIA -) - STREAM_FORMAT_TO_MEDIA_TYPE = { "dash": MEDIA_TYPE_VIDEO, @@ -137,6 +113,20 @@ async def async_setup_entry( class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """Representation of a Roku media player on the network.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) + def __init__( self, unique_id: str | None, coordinator: RokuDataUpdateCoordinator ) -> None: @@ -148,7 +138,6 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): self._attr_name = coordinator.data.info.name self._attr_unique_id = unique_id - self._attr_supported_features = SUPPORT_ROKU def _media_playback_trackable(self) -> bool: """Detect if we have enough media data to track playback.""" diff --git a/homeassistant/components/roku/translations/hu.json b/homeassistant/components/roku/translations/hu.json index 256a8d45997..c28b0a712ea 100644 --- a/homeassistant/components/roku/translations/hu.json +++ b/homeassistant/components/roku/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 8c2d9d4e84a..9f42c49c74f 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -12,16 +12,16 @@ "step": { "discovery_confirm": { "data": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "description": "Vuoi configurare {name}?", "title": "Roku" }, "ssdp_confirm": { "data": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "description": "Vuoi impostare {name}?", "title": "Roku" diff --git a/homeassistant/components/roomba/braava.py b/homeassistant/components/roomba/braava.py index 90298078e42..ea08829cba6 100644 --- a/homeassistant/components/roomba/braava.py +++ b/homeassistant/components/roomba/braava.py @@ -1,7 +1,7 @@ """Class for Braava devices.""" import logging -from homeassistant.components.vacuum import SUPPORT_FAN_SPEED +from homeassistant.components.vacuum import VacuumEntityFeature from .irobot_base import SUPPORT_IROBOT, IRobotVacuum @@ -23,7 +23,7 @@ BRAAVA_MOP_BEHAVIORS = [MOP_STANDARD, MOP_DEEP, MOP_EXTENDED] BRAAVA_SPRAY_AMOUNT = [1, 2, 3] # Braava Jets can set mopping behavior through fanspeed -SUPPORT_BRAAVA = SUPPORT_IROBOT | SUPPORT_FAN_SPEED +SUPPORT_BRAAVA = SUPPORT_IROBOT | VacuumEntityFeature.FAN_SPEED class BraavaJet(IRobotVacuum): diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index d7e3266f5fa..dd076f6fb63 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -10,16 +10,8 @@ from homeassistant.components.vacuum import ( STATE_DOCKED, STATE_ERROR, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STATUS, - SUPPORT_STOP, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.const import STATE_IDLE, STATE_PAUSED import homeassistant.helpers.device_registry as dr @@ -40,15 +32,15 @@ ATTR_SOFTWARE_VERSION = "software_version" # Commonly supported features SUPPORT_IROBOT = ( - SUPPORT_BATTERY - | SUPPORT_PAUSE - | SUPPORT_RETURN_HOME - | SUPPORT_SEND_COMMAND - | SUPPORT_START - | SUPPORT_STATE - | SUPPORT_STATUS - | SUPPORT_STOP - | SUPPORT_LOCATE + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.STOP + | VacuumEntityFeature.LOCATE ) STATE_MAP = { @@ -132,7 +124,7 @@ class IRobotEntity(Entity): """Register callback function.""" self.vacuum.register_on_message_callback(self.on_message) - def new_state_filter(self, new_state): # pylint: disable=no-self-use + def new_state_filter(self, new_state): """Filter out wifi state messages.""" return len(new_state) > 1 or "signal" not in new_state diff --git a/homeassistant/components/roomba/roomba.py b/homeassistant/components/roomba/roomba.py index 5f960aeaae0..7cac9a3ba52 100644 --- a/homeassistant/components/roomba/roomba.py +++ b/homeassistant/components/roomba/roomba.py @@ -1,7 +1,7 @@ """Class for Roomba devices.""" import logging -from homeassistant.components.vacuum import SUPPORT_FAN_SPEED +from homeassistant.components.vacuum import VacuumEntityFeature from .irobot_base import SUPPORT_IROBOT, IRobotVacuum @@ -16,7 +16,7 @@ FAN_SPEED_PERFORMANCE = "Performance" FAN_SPEEDS = [FAN_SPEED_AUTOMATIC, FAN_SPEED_ECO, FAN_SPEED_PERFORMANCE] # Only Roombas with CarpetBost can set their fanspeed -SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_IROBOT | SUPPORT_FAN_SPEED +SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_IROBOT | VacuumEntityFeature.FAN_SPEED class RoombaVacuum(IRobotVacuum): diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index c56c00a98b0..d2d76bee5ff 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -16,7 +16,7 @@ "host": "Nazwa hosta lub adres IP" }, "description": "Wybierz Roomb\u0119 lub Braava", - "title": "Po\u0142\u0105cz si\u0119 automatycznie z urz\u0105dzeniem" + "title": "Automatyczne po\u0142\u0105czenie z urz\u0105dzeniem" }, "link": { "description": "Naci\u015bnij i przytrzymaj przycisk Home na {name} a\u017c urz\u0105dzenie wygeneruje d\u017awi\u0119k (oko\u0142o dwie sekundy), a nast\u0119pnie zatwierd\u017a w ci\u0105gu 30 sekund.", @@ -46,7 +46,7 @@ "password": "Has\u0142o" }, "description": "Wybierz Roomb\u0119 lub Braava", - "title": "Po\u0142\u0105cz si\u0119 automatycznie z urz\u0105dzeniem" + "title": "Automatyczne po\u0142\u0105czenie z urz\u0105dzeniem" } } }, diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index 7c280727a19..3651bd9ec05 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -4,23 +4,9 @@ import logging from roonapi import split_media_path import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_BROWSE_MEDIA, - SUPPORT_GROUPING, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -44,24 +30,6 @@ from homeassistant.util.dt import utcnow from .const import DOMAIN from .media_browser import browse_media -SUPPORT_ROON = ( - SUPPORT_BROWSE_MEDIA - | SUPPORT_GROUPING - | SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_STOP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SHUFFLE_SET - | SUPPORT_SEEK - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_MUTE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_VOLUME_STEP -) - _LOGGER = logging.getLogger(__name__) SERVICE_TRANSFER = "transfer" @@ -108,6 +76,24 @@ async def async_setup_entry( class RoonDevice(MediaPlayerEntity): """Representation of an Roon device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.GROUPING + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.VOLUME_STEP + ) + def __init__(self, server, player_data): """Initialize Roon device object.""" self._remove_signal_status = None @@ -154,11 +140,6 @@ class RoonDevice(MediaPlayerEntity): """Return True if entity is available.""" return self._available - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_ROON - @property def group_members(self): """Return the grouped players.""" diff --git a/homeassistant/components/roon/translations/ca.json b/homeassistant/components/roon/translations/ca.json index f05ac1a4acb..3f601ded490 100644 --- a/homeassistant/components/roon/translations/ca.json +++ b/homeassistant/components/roon/translations/ca.json @@ -8,6 +8,13 @@ "unknown": "Error inesperat" }, "step": { + "fallback": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port" + }, + "description": "No s'ha pogut descobrir el servidor Roon, introdueix l'amfitri\u00f3 i el port." + }, "link": { "description": "Has d'autoritzar Home Assistant a Roon. Despr\u00e9s de fer clic a envia, ves a l'aplicaci\u00f3 Roon Core, obre la Configuraci\u00f3 i activa Home Assistant a la pestanya d'Extensions.", "title": "Autoritza Home Assistant a Roon" diff --git a/homeassistant/components/roon/translations/de.json b/homeassistant/components/roon/translations/de.json index 4eadf9c363a..e41fe77c3cd 100644 --- a/homeassistant/components/roon/translations/de.json +++ b/homeassistant/components/roon/translations/de.json @@ -8,6 +8,13 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Der Roon-Server konnte nicht gefunden werden, bitte gib deinen Hostnamen und Port ein." + }, "link": { "description": "Du musst den Home Assistant in Roon autorisieren. Nachdem du auf \"Submit\" geklickt hast, gehe zur Roon Core-Anwendung, \u00f6ffne die Einstellungen und aktiviere HomeAssistant auf der Registerkarte \"Extensions\".", "title": "HomeAssistant in Roon autorisieren" diff --git a/homeassistant/components/roon/translations/el.json b/homeassistant/components/roon/translations/el.json index e88cbed685c..63cda0821b1 100644 --- a/homeassistant/components/roon/translations/el.json +++ b/homeassistant/components/roon/translations/el.json @@ -8,6 +8,13 @@ "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { + "fallback": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "description": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c2 \u03bf \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Roon, \u03b5\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03c3\u03b1\u03c2." + }, "link": { "description": "\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant \u03c3\u03c4\u03bf Roon. \u0391\u03c6\u03bf\u03cd \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae, \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae Roon Core, \u03b1\u03bd\u03bf\u03af\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf HomeAssistant \u03c3\u03c4\u03b7\u03bd \u03ba\u03b1\u03c1\u03c4\u03ad\u03bb\u03b1 \u0395\u03c0\u03b5\u03ba\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2.", "title": "\u0395\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf HomeAssistant \u03c3\u03c4\u03bf Roon" diff --git a/homeassistant/components/roon/translations/en.json b/homeassistant/components/roon/translations/en.json index d1f86fbce5f..b0affedc026 100644 --- a/homeassistant/components/roon/translations/en.json +++ b/homeassistant/components/roon/translations/en.json @@ -8,6 +8,13 @@ "unknown": "Unexpected error" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Could not discover Roon server, please enter your Hostname and Port." + }, "link": { "description": "You must authorize Home Assistant in Roon. After you click submit, go to the Roon Core application, open Settings and enable HomeAssistant on the Extensions tab.", "title": "Authorize HomeAssistant in Roon" diff --git a/homeassistant/components/roon/translations/et.json b/homeassistant/components/roon/translations/et.json index bbdf2b5edc5..d091f8e4064 100644 --- a/homeassistant/components/roon/translations/et.json +++ b/homeassistant/components/roon/translations/et.json @@ -8,6 +8,13 @@ "unknown": "Tundmatu viga" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Rooni serverit ei leitud, sisesta hostinimi ja port." + }, "link": { "description": "Pead Roonis koduabilise volitama. Kui oled kl\u00f5psanud nuppu Esita, mine rakendusse Roon Core, ava Seaded ja luba vahekaardil Laiendused Home Assistant.", "title": "Volita HomeAssistant Roonis" diff --git a/homeassistant/components/roon/translations/fr.json b/homeassistant/components/roon/translations/fr.json index b0fb3e6784b..7af98de188a 100644 --- a/homeassistant/components/roon/translations/fr.json +++ b/homeassistant/components/roon/translations/fr.json @@ -8,6 +8,13 @@ "unknown": "Erreur inattendue" }, "step": { + "fallback": { + "data": { + "host": "H\u00f4te", + "port": "Port" + }, + "description": "Impossible de trouver le serveur Roon, veuillez saisir le nom de l'h\u00f4te et le port." + }, "link": { "description": "Vous devez autoriser Home Assistant dans Roon. Apr\u00e8s avoir cliqu\u00e9 sur soumettre, acc\u00e9dez \u00e0 l'application Roon Core, ouvrez Param\u00e8tres et activez Home Assistant dans l'onglet Extensions.", "title": "Autoriser Home Assistant dans Roon" diff --git a/homeassistant/components/roon/translations/he.json b/homeassistant/components/roon/translations/he.json index 9d3230d257e..05781910772 100644 --- a/homeassistant/components/roon/translations/he.json +++ b/homeassistant/components/roon/translations/he.json @@ -8,6 +8,12 @@ "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" }, "step": { + "fallback": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "port": "\u05e4\u05ea\u05d7\u05d4" + } + }, "link": { "description": "\u05e2\u05dc\u05d9\u05da \u05dc\u05d0\u05e9\u05e8 \u05dc-Home Assistant \u05d1-Roon. \u05dc\u05d0\u05d7\u05e8 \u05e9\u05ea\u05dc\u05d7\u05e5 \u05e2\u05dc \u05e9\u05dc\u05d7, \u05e2\u05d1\u05d5\u05e8 \u05dc\u05d9\u05d9\u05e9\u05d5\u05dd Roon Core, \u05e4\u05ea\u05d7 \u05d0\u05ea \u05d4\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d5\u05d4\u05e4\u05e2\u05dc \u05d0\u05ea HomeAssistant \u05d1\u05db\u05e8\u05d8\u05d9\u05e1\u05d9\u05d9\u05d4 \u05d4\u05e8\u05d7\u05d1\u05d5\u05ea." }, diff --git a/homeassistant/components/roon/translations/hu.json b/homeassistant/components/roon/translations/hu.json index 7d2b63f0f4b..3a9bcf3f522 100644 --- a/homeassistant/components/roon/translations/hu.json +++ b/homeassistant/components/roon/translations/hu.json @@ -8,15 +8,24 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "fallback": { + "data": { + "host": "C\u00edm", + "port": "Port" + }, + "description": "Nem siker\u00fclt felfedezni a Roon-kiszolg\u00e1l\u00f3t. K\u00e9rj\u00fck, adja meg g\u00e9p c\u00edm\u00e9t \u00e9s portj\u00e1t." + }, "link": { - "description": "Enged\u00e9lyeznie kell az Home Assistantot a Roonban. Miut\u00e1n r\u00e1kattintott a K\u00fcld\u00e9s gombra, nyissa meg a Roon Core alkalmaz\u00e1st, nyissa meg a Be\u00e1ll\u00edt\u00e1sokat, \u00e9s enged\u00e9lyezze a Home Assistant funkci\u00f3t a B\u0151v\u00edtm\u00e9nyek lapon.", + "description": "Enged\u00e9lyeznie kell az Home Assistantot a Roonban. Miut\u00e1n r\u00e1kattintott a Mehet gombra, nyissa meg a Roon Core alkalmaz\u00e1st, nyissa meg a Be\u00e1ll\u00edt\u00e1sokat, \u00e9s enged\u00e9lyezze a Home Assistant funkci\u00f3t a B\u0151v\u00edtm\u00e9nyek lapon.", "title": "Enged\u00e9lyezze a Home Assistant alkalmaz\u00e1st Roon-ban" }, "user": { "data": { "host": "C\u00edm" }, - "description": "A Roon szerver nem tal\u00e1lhat\u00f3, adja meg a hosztnev\u00e9t vagy c\u00edm\u00e9t" + "description": "A Roon szerver nem tal\u00e1lhat\u00f3, adja meg a hosztnev\u00e9t vagy c\u00edm\u00e9t", + "one": "\u00dcres", + "other": "\u00dcres" } } } diff --git a/homeassistant/components/roon/translations/id.json b/homeassistant/components/roon/translations/id.json index 96b26640320..85c1519e7be 100644 --- a/homeassistant/components/roon/translations/id.json +++ b/homeassistant/components/roon/translations/id.json @@ -8,6 +8,13 @@ "unknown": "Kesalahan yang tidak diharapkan" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Tidak dapat menemukan server Roon, masukkan Nama Host dan Port Anda." + }, "link": { "description": "Anda harus mengotorisasi Home Assistant di Roon. Setelah Anda mengeklik kirim, buka aplikasi Roon Core, buka Pengaturan dan aktifkan HomeAssistant pada tab Ekstensi.", "title": "Otorisasi HomeAssistant di Roon" diff --git a/homeassistant/components/roon/translations/it.json b/homeassistant/components/roon/translations/it.json index ac5922e1e56..3236ebd0bb2 100644 --- a/homeassistant/components/roon/translations/it.json +++ b/homeassistant/components/roon/translations/it.json @@ -8,6 +8,13 @@ "unknown": "Errore imprevisto" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Porta" + }, + "description": "Impossibile scoprire il server Roon, inserisci il tuo nome host e la porta." + }, "link": { "description": "Devi autorizzare l'Assistente Home in Roon. Dopo aver fatto clic su Invia, passa all'applicazione Roon Core, apri Impostazioni e abilita HomeAssistant nella scheda Estensioni.", "title": "Autorizza HomeAssistant in Roon" @@ -16,7 +23,9 @@ "data": { "host": "Host" }, - "description": "Impossibile individuare il server Roon, inserire l'hostname o l'IP." + "description": "Impossibile individuare il server Roon, inserire l'hostname o l'IP.", + "one": "Vuoto", + "other": "Vuoti" } } } diff --git a/homeassistant/components/roon/translations/ja.json b/homeassistant/components/roon/translations/ja.json index dba8ccc9237..400d982d27c 100644 --- a/homeassistant/components/roon/translations/ja.json +++ b/homeassistant/components/roon/translations/ja.json @@ -8,6 +8,13 @@ "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { + "fallback": { + "data": { + "host": "\u30db\u30b9\u30c8", + "port": "\u30dd\u30fc\u30c8" + }, + "description": "Roon\u30b5\u30fc\u30d0\u30fc\u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30db\u30b9\u30c8\u540d\u3068\u30dd\u30fc\u30c8\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, "link": { "description": "Roon\u3067Home Assistant\u3092\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\u9001\u4fe1(submit) \u3092\u30af\u30ea\u30c3\u30af\u3057\u305f\u5f8c\u3001Roon Core\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3067\u3001\u8a2d\u5b9a(Settings )\u3092\u958b\u304d\u3001\u6a5f\u80fd\u62e1\u5f35\u30bf\u30d6(extensions tab)\u3067Home Assistant\u3092\u6709\u52b9(enable )\u306b\u3057\u307e\u3059\u3002", "title": "Roon\u3067HomeAssistant\u3092\u8a8d\u8a3c\u3059\u308b" diff --git a/homeassistant/components/roon/translations/nl.json b/homeassistant/components/roon/translations/nl.json index df8fa80b4dd..9aafadc918b 100644 --- a/homeassistant/components/roon/translations/nl.json +++ b/homeassistant/components/roon/translations/nl.json @@ -8,6 +8,13 @@ "unknown": "Onverwachte fout" }, "step": { + "fallback": { + "data": { + "host": "Host", + "port": "Poort" + }, + "description": "Kon de Roon server niet vinden, voer uw Hostnaam en Poort in." + }, "link": { "description": "U moet Home Assistant autoriseren in Roon. Nadat je op verzenden hebt geklikt, ga je naar de Roon Core-applicatie, open je Instellingen en schakel je Home Assistant in op het tabblad Extensies.", "title": "Autoriseer Home Assistant in Roon" diff --git a/homeassistant/components/roon/translations/no.json b/homeassistant/components/roon/translations/no.json index 2558c2c27c7..11eb60a04ce 100644 --- a/homeassistant/components/roon/translations/no.json +++ b/homeassistant/components/roon/translations/no.json @@ -8,6 +8,13 @@ "unknown": "Uventet feil" }, "step": { + "fallback": { + "data": { + "host": "Vert", + "port": "Port" + }, + "description": "Kunne ikke finne Roon-serveren, skriv inn vertsnavnet og porten." + }, "link": { "description": "Du m\u00e5 godkjenne Home Assistant i Roon. N\u00e5r du klikker send inn, g\u00e5r du til Roon Core-programmet, \u00e5pner innstillingene og aktiverer Home Assistant p\u00e5 utvidelser-fanen.", "title": "Autoriser Home Assistant i Roon" diff --git a/homeassistant/components/roon/translations/pl.json b/homeassistant/components/roon/translations/pl.json index fab5fb9657e..b45a7fcb562 100644 --- a/homeassistant/components/roon/translations/pl.json +++ b/homeassistant/components/roon/translations/pl.json @@ -8,6 +8,13 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "fallback": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port" + }, + "description": "Nie mo\u017cna wykry\u0107 serwera Roon, wprowad\u017a nazw\u0119 hosta i port." + }, "link": { "description": "Musisz autoryzowa\u0107 Home Assistant w Roon. Po klikni\u0119ciu przycisku \"Zatwierd\u017a\", przejd\u017a do aplikacji Roon Core, otw\u00f3rz \"Ustawienia\" i w\u0142\u0105cz Home Assistant w karcie \"Rozszerzenia\" (Extensions).", "title": "Autoryzuj Home Assistant w Roon" diff --git a/homeassistant/components/roon/translations/pt-BR.json b/homeassistant/components/roon/translations/pt-BR.json index a96222b15f3..85628df4a7c 100644 --- a/homeassistant/components/roon/translations/pt-BR.json +++ b/homeassistant/components/roon/translations/pt-BR.json @@ -8,6 +8,13 @@ "unknown": "Erro inesperado" }, "step": { + "fallback": { + "data": { + "host": "Nome do host", + "port": "Porta" + }, + "description": "N\u00e3o foi poss\u00edvel descobrir o servidor Roon, digite seu nome de host e porta." + }, "link": { "description": "Voc\u00ea deve autorizar o Home Assistant no Roon. Depois de clicar em enviar, v\u00e1 para o aplicativo Roon principal, abra Configura\u00e7\u00f5es e habilite o HomeAssistant na aba Extens\u00f5es.", "title": "Autorizar HomeAssistant no Roon" diff --git a/homeassistant/components/roon/translations/ru.json b/homeassistant/components/roon/translations/ru.json index 1bcb07c7695..cc89dc2d2dd 100644 --- a/homeassistant/components/roon/translations/ru.json +++ b/homeassistant/components/roon/translations/ru.json @@ -8,6 +8,13 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { + "fallback": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Roon, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438 \u043f\u043e\u0440\u0442." + }, "link": { "description": "\u041f\u043e\u0441\u043b\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u00ab\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c\u00bb \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Roon Core, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u00ab\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u00bb \u0438 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435 HomeAssistant \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u00ab\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u00bb.", "title": "Roon" diff --git a/homeassistant/components/roon/translations/tr.json b/homeassistant/components/roon/translations/tr.json index 75fedf6383e..c18df90b9b8 100644 --- a/homeassistant/components/roon/translations/tr.json +++ b/homeassistant/components/roon/translations/tr.json @@ -8,6 +8,13 @@ "unknown": "Beklenmeyen hata" }, "step": { + "fallback": { + "data": { + "host": "Sunucu", + "port": "Port" + }, + "description": "Roon sunucusu bulunamad\u0131, l\u00fctfen Ana Bilgisayar Ad\u0131n\u0131z\u0131 ve Ba\u011flant\u0131 Noktan\u0131z\u0131 girin." + }, "link": { "description": "Roon'da HomeAssistant\u0131 yetkilendirmelisiniz. G\u00f6nder'e t\u0131klad\u0131ktan sonra, Roon Core uygulamas\u0131na gidin, Ayarlar'\u0131 a\u00e7\u0131n ve Uzant\u0131lar sekmesinde HomeAssistant'\u0131 etkinle\u015ftirin.", "title": "Roon'da HomeAssistant'\u0131 Yetkilendirme" diff --git a/homeassistant/components/roon/translations/zh-Hant.json b/homeassistant/components/roon/translations/zh-Hant.json index f8f52d11b1c..f887b2d9847 100644 --- a/homeassistant/components/roon/translations/zh-Hant.json +++ b/homeassistant/components/roon/translations/zh-Hant.json @@ -8,6 +8,13 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "fallback": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u627e\u4e0d\u5230 Roon \u4f3a\u670d\u5668\uff0c\u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31\u53ca\u901a\u8a0a\u57e0\u3002" + }, "link": { "description": "\u5fc5\u9808\u65bc Roon \u4e2d\u8a8d\u8b49 Home Assistant\u3002\u9ede\u9078\u50b3\u9001\u5f8c\u3001\u958b\u555f Roon Core \u61c9\u7528\u7a0b\u5f0f\u3001\u6253\u958b\u8a2d\u5b9a\u4e26\u65bc\u64f4\u5145\uff08Extensions\uff09\u4e2d\u555f\u7528 HomeAssistant\u3002", "title": "\u65bc Roon \u4e2d\u8a8d\u8b49 HomeAssistant" diff --git a/homeassistant/components/rtsp_to_webrtc/translations/hu.json b/homeassistant/components/rtsp_to_webrtc/translations/hu.json index 75c471a6710..5da10dd88e4 100644 --- a/homeassistant/components/rtsp_to_webrtc/translations/hu.json +++ b/homeassistant/components/rtsp_to_webrtc/translations/hu.json @@ -2,7 +2,8 @@ "config": { "abort": { "server_failure": "Az RTSPtoWebRTC szerver hib\u00e1t jelzett vissza. Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt ellen\u0151rizze a napl\u00f3kat.", - "server_unreachable": "Nem lehet kommunik\u00e1lni az RTSPtoWebRTC szerverrel. Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt ellen\u0151rizze a napl\u00f3kat." + "server_unreachable": "Nem lehet kommunik\u00e1lni az RTSPtoWebRTC szerverrel. Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt ellen\u0151rizze a napl\u00f3kat.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "invalid_url": "\u00c9rv\u00e9nyes RTSPtoWebRTC szerver URL-nek kell lennie, pl. https://example.com", diff --git a/homeassistant/components/russound_rio/media_player.py b/homeassistant/components/russound_rio/media_player.py index f8ebf39d22f..054448cd9ad 100644 --- a/homeassistant/components/russound_rio/media_player.py +++ b/homeassistant/components/russound_rio/media_player.py @@ -4,15 +4,12 @@ from __future__ import annotations from russound_rio import Russound import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,14 +23,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -SUPPORT_RUSSOUND = ( - SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -81,6 +70,14 @@ async def async_setup_platform( class RussoundZoneDevice(MediaPlayerEntity): """Representation of a Russound Zone.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, russ, zone_id, name, sources): """Initialize the zone device.""" super().__init__() @@ -141,11 +138,6 @@ class RussoundZoneDevice(MediaPlayerEntity): if status == "OFF": return STATE_OFF - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_RUSSOUND - @property def source(self): """Get the currently selected source.""" diff --git a/homeassistant/components/russound_rnet/media_player.py b/homeassistant/components/russound_rnet/media_player.py index 0376bc3a448..b97b431333f 100644 --- a/homeassistant/components/russound_rnet/media_player.py +++ b/homeassistant/components/russound_rnet/media_player.py @@ -6,13 +6,10 @@ import logging from russound import russound import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant @@ -25,13 +22,6 @@ _LOGGER = logging.getLogger(__name__) CONF_ZONES = "zones" CONF_SOURCES = "sources" -SUPPORT_RUSSOUND = ( - SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE -) ZONE_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) @@ -81,6 +71,14 @@ def setup_platform( class RussoundRNETDevice(MediaPlayerEntity): """Representation of a Russound RNET device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, hass, russ, sources, zone_id, extra): """Initialise the Russound RNET device.""" self._name = extra["name"] @@ -129,11 +127,6 @@ class RussoundRNETDevice(MediaPlayerEntity): """Return the state of the device.""" return self._state - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_RUSSOUND - @property def source(self): """Get the currently selected source.""" diff --git a/homeassistant/components/sabnzbd/__init__.py b/homeassistant/components/sabnzbd/__init__.py index e8da8738b5b..aca50e404a2 100644 --- a/homeassistant/components/sabnzbd/__init__.py +++ b/homeassistant/components/sabnzbd/__init__.py @@ -1,16 +1,11 @@ """Support for monitoring an SABnzbd NZB client.""" -from __future__ import annotations - -from dataclasses import dataclass -from datetime import timedelta +from collections.abc import Callable import logging -from pysabnzbd import SabnzbdApi, SabnzbdApiException +from pysabnzbd import SabnzbdApiException import voluptuous as vol -from homeassistant.components import configurator -from homeassistant.components.discovery import SERVICE_SABNZBD -from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryState from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -19,242 +14,173 @@ from homeassistant.const import ( CONF_PORT, CONF_SENSORS, CONF_SSL, - DATA_GIGABYTES, - DATA_MEGABYTES, - DATA_RATE_MEGABYTES_PER_SECOND, - Platform, + CONF_URL, ) from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.helpers import discovery -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.util.json import load_json, save_json +from homeassistant.helpers.typing import ConfigType +from .const import ( + ATTR_API_KEY, + ATTR_SPEED, + DEFAULT_HOST, + DEFAULT_NAME, + DEFAULT_PORT, + DEFAULT_SPEED_LIMIT, + DEFAULT_SSL, + DOMAIN, + KEY_API, + KEY_API_DATA, + KEY_NAME, + SERVICE_PAUSE, + SERVICE_RESUME, + SERVICE_SET_SPEED, + SIGNAL_SABNZBD_UPDATED, + UPDATE_INTERVAL, +) +from .sab import get_client +from .sensor import SENSOR_KEYS + +PLATFORMS = ["sensor"] _LOGGER = logging.getLogger(__name__) -DOMAIN = "sabnzbd" -DATA_SABNZBD = "sabznbd" - -_CONFIGURING: dict[str, str] = {} - -ATTR_SPEED = "speed" -BASE_URL_FORMAT = "{}://{}:{}/" -CONFIG_FILE = "sabnzbd.conf" -DEFAULT_HOST = "localhost" -DEFAULT_NAME = "SABnzbd" -DEFAULT_PORT = 8080 -DEFAULT_SPEED_LIMIT = "100" -DEFAULT_SSL = False - -UPDATE_INTERVAL = timedelta(seconds=30) - -SERVICE_PAUSE = "pause" -SERVICE_RESUME = "resume" -SERVICE_SET_SPEED = "set_speed" - -SIGNAL_SABNZBD_UPDATED = "sabnzbd_updated" - - -@dataclass -class SabnzbdRequiredKeysMixin: - """Mixin for required keys.""" - - field_name: str - - -@dataclass -class SabnzbdSensorEntityDescription(SensorEntityDescription, SabnzbdRequiredKeysMixin): - """Describes Sabnzbd sensor entity.""" - - -SENSOR_TYPES: tuple[SabnzbdSensorEntityDescription, ...] = ( - SabnzbdSensorEntityDescription( - key="current_status", - name="Status", - field_name="status", - ), - SabnzbdSensorEntityDescription( - key="speed", - name="Speed", - native_unit_of_measurement=DATA_RATE_MEGABYTES_PER_SECOND, - field_name="kbpersec", - ), - SabnzbdSensorEntityDescription( - key="queue_size", - name="Queue", - native_unit_of_measurement=DATA_MEGABYTES, - field_name="mb", - ), - SabnzbdSensorEntityDescription( - key="queue_remaining", - name="Left", - native_unit_of_measurement=DATA_MEGABYTES, - field_name="mbleft", - ), - SabnzbdSensorEntityDescription( - key="disk_size", - name="Disk", - native_unit_of_measurement=DATA_GIGABYTES, - field_name="diskspacetotal1", - ), - SabnzbdSensorEntityDescription( - key="disk_free", - name="Disk Free", - native_unit_of_measurement=DATA_GIGABYTES, - field_name="diskspace1", - ), - SabnzbdSensorEntityDescription( - key="queue_count", - name="Queue Count", - field_name="noofslots_total", - ), - SabnzbdSensorEntityDescription( - key="day_size", - name="Daily Total", - native_unit_of_measurement=DATA_GIGABYTES, - field_name="day_size", - ), - SabnzbdSensorEntityDescription( - key="week_size", - name="Weekly Total", - native_unit_of_measurement=DATA_GIGABYTES, - field_name="week_size", - ), - SabnzbdSensorEntityDescription( - key="month_size", - name="Monthly Total", - native_unit_of_measurement=DATA_GIGABYTES, - field_name="month_size", - ), - SabnzbdSensorEntityDescription( - key="total_size", - name="Total", - native_unit_of_measurement=DATA_GIGABYTES, - field_name="total_size", - ), +SERVICES = ( + SERVICE_PAUSE, + SERVICE_RESUME, + SERVICE_SET_SPEED, ) -SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES] +SERVICE_BASE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_API_KEY): cv.string, + } +) -SPEED_LIMIT_SCHEMA = vol.Schema( - {vol.Optional(ATTR_SPEED, default=DEFAULT_SPEED_LIMIT): cv.string} +SERVICE_SPEED_SCHEMA = SERVICE_BASE_SCHEMA.extend( + { + vol.Optional(ATTR_SPEED, default=DEFAULT_SPEED_LIMIT): cv.string, + } ) CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( - { - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PATH): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_SENSORS): vol.All( - cv.ensure_list, [vol.In(SENSOR_KEYS)] - ), - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - } + vol.All( + cv.deprecated(CONF_HOST), + cv.deprecated(CONF_PORT), + cv.deprecated(CONF_SENSORS), + cv.deprecated(CONF_SSL), + { + vol.Required(CONF_API_KEY): str, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + vol.Required(CONF_URL): str, + vol.Optional(CONF_PATH): str, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [vol.In(SENSOR_KEYS)] + ), + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + }, + ) ) }, extra=vol.ALLOW_EXTRA, ) -async def async_check_sabnzbd(sab_api): - """Check if we can reach SABnzbd.""" - - try: - await sab_api.check_available() - return True - except SabnzbdApiException: - _LOGGER.error("Connection to SABnzbd API failed") - return False - - -async def async_configure_sabnzbd( - hass, config, use_ssl, name=DEFAULT_NAME, api_key=None -): - """Try to configure Sabnzbd and request api key if configuration fails.""" - - host = config[CONF_HOST] - port = config[CONF_PORT] - web_root = config.get(CONF_PATH) - uri_scheme = "https" if use_ssl else "http" - base_url = BASE_URL_FORMAT.format(uri_scheme, host, port) - if api_key is None: - conf = await hass.async_add_executor_job( - load_json, hass.config.path(CONFIG_FILE) - ) - api_key = conf.get(base_url, {}).get(CONF_API_KEY, "") - - sab_api = SabnzbdApi( - base_url, api_key, web_root=web_root, session=async_get_clientsession(hass) - ) - if await async_check_sabnzbd(sab_api): - async_setup_sabnzbd(hass, sab_api, config, name) - else: - async_request_configuration(hass, config, base_url, web_root) - - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the SABnzbd component.""" + hass.data.setdefault(DOMAIN, {}) - async def sabnzbd_discovered(service: str, info: DiscoveryInfoType | None) -> None: - """Handle service discovery.""" - if not info: - return - ssl = info.get("properties", {}).get("https", "0") == "1" - await async_configure_sabnzbd(hass, info, ssl) + if hass.config_entries.async_entries(DOMAIN): + return True - discovery.async_listen(hass, SERVICE_SABNZBD, sabnzbd_discovered) + if DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config[DOMAIN], + ) + ) - if (conf := config.get(DOMAIN)) is not None: - use_ssl = conf[CONF_SSL] - name = conf.get(CONF_NAME) - api_key = conf.get(CONF_API_KEY) - await async_configure_sabnzbd(hass, conf, use_ssl, name, api_key) return True @callback -def async_setup_sabnzbd(hass, sab_api, config, name): - """Set up SABnzbd sensors and services.""" - sab_api_data = SabnzbdApiData(sab_api, name, config.get(CONF_SENSORS, {})) +def async_get_entry_id_for_service_call(hass: HomeAssistant, call: ServiceCall) -> str: + """Get the entry ID related to a service call (by device ID).""" + call_data_api_key = call.data[ATTR_API_KEY] - if config.get(CONF_SENSORS): - hass.data[DATA_SABNZBD] = sab_api_data - hass.async_create_task( - discovery.async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) - ) + for entry in hass.config_entries.async_entries(DOMAIN): + if entry.data[ATTR_API_KEY] == call_data_api_key: + return entry.entry_id - async def async_service_handler(service: ServiceCall) -> None: - """Handle service calls.""" - if service.service == SERVICE_PAUSE: - await sab_api_data.async_pause_queue() - elif service.service == SERVICE_RESUME: - await sab_api_data.async_resume_queue() - elif service.service == SERVICE_SET_SPEED: - speed = service.data.get(ATTR_SPEED) - await sab_api_data.async_set_queue_speed(speed) + raise ValueError(f"No api for API key: {call_data_api_key}") - hass.services.async_register( - DOMAIN, SERVICE_PAUSE, async_service_handler, schema=vol.Schema({}) - ) - hass.services.async_register( - DOMAIN, SERVICE_RESUME, async_service_handler, schema=vol.Schema({}) - ) +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up the SabNzbd Component.""" + sab_api = await get_client(hass, entry.data) + if not sab_api: + raise ConfigEntryNotReady - hass.services.async_register( - DOMAIN, SERVICE_SET_SPEED, async_service_handler, schema=SPEED_LIMIT_SCHEMA - ) + sab_api_data = SabnzbdApiData(sab_api) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + KEY_API: sab_api, + KEY_API_DATA: sab_api_data, + KEY_NAME: entry.data[CONF_NAME], + } + + @callback + def extract_api(func: Callable) -> Callable: + """Define a decorator to get the correct api for a service call.""" + + async def wrapper(call: ServiceCall) -> None: + """Wrap the service function.""" + entry_id = async_get_entry_id_for_service_call(hass, call) + api_data = hass.data[DOMAIN][entry_id][KEY_API_DATA] + + try: + await func(call, api_data) + except Exception as err: + raise HomeAssistantError( + f"Error while executing {func.__name__}: {err}" + ) from err + + return wrapper + + @extract_api + async def async_pause_queue(call: ServiceCall, api: SabnzbdApiData) -> None: + await api.async_pause_queue() + + @extract_api + async def async_resume_queue(call: ServiceCall, api: SabnzbdApiData) -> None: + await api.async_resume_queue() + + @extract_api + async def async_set_queue_speed(call: ServiceCall, api: SabnzbdApiData) -> None: + speed = call.data.get(ATTR_SPEED) + await api.async_set_queue_speed(speed) + + for service, method, schema in ( + (SERVICE_PAUSE, async_pause_queue, SERVICE_BASE_SCHEMA), + (SERVICE_RESUME, async_resume_queue, SERVICE_BASE_SCHEMA), + (SERVICE_SET_SPEED, async_set_queue_speed, SERVICE_SPEED_SCHEMA), + ): + + if hass.services.has_service(DOMAIN, service): + continue + + hass.services.async_register(DOMAIN, service, method, schema=schema) async def async_update_sabnzbd(now): """Refresh SABnzbd queue data.""" - try: await sab_api.refresh_data() async_dispatcher_send(hass, SIGNAL_SABNZBD_UPDATED, None) @@ -262,58 +188,37 @@ def async_setup_sabnzbd(hass, sab_api, config, name): _LOGGER.error(err) async_track_time_interval(hass, async_update_sabnzbd, UPDATE_INTERVAL) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True -@callback -def async_request_configuration(hass, config, host, web_root): - """Request configuration steps from the user.""" +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a Sabnzbd config entry.""" + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) - # We got an error if this method is called while we are configuring - if host in _CONFIGURING: - configurator.async_notify_errors( - hass, _CONFIGURING[host], "Failed to register, please try again." - ) + loaded_entries = [ + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.state == ConfigEntryState.LOADED + ] + if len(loaded_entries) == 1: + # If this is the last loaded instance of Sabnzbd, deregister any services + # defined during integration setup: + for service_name in SERVICES: + hass.services.async_remove(DOMAIN, service_name) - return - - async def async_configuration_callback(data): - """Handle configuration changes.""" - api_key = data.get(CONF_API_KEY) - sab_api = SabnzbdApi( - host, api_key, web_root=web_root, session=async_get_clientsession(hass) - ) - if not await async_check_sabnzbd(sab_api): - return - - def success(): - """Signal successful setup.""" - conf = load_json(hass.config.path(CONFIG_FILE)) - conf[host] = {CONF_API_KEY: api_key} - save_json(hass.config.path(CONFIG_FILE), conf) - req_config = _CONFIGURING.pop(host) - configurator.request_done(hass, req_config) - - hass.async_add_job(success) - async_setup_sabnzbd(hass, sab_api, config, config.get(CONF_NAME, DEFAULT_NAME)) - - _CONFIGURING[host] = configurator.async_request_config( - hass, - DEFAULT_NAME, - async_configuration_callback, - description="Enter the API Key", - submit_caption="Confirm", - fields=[{"id": CONF_API_KEY, "name": "API Key", "type": ""}], - ) + return unload_ok class SabnzbdApiData: """Class for storing/refreshing sabnzbd api queue data.""" - def __init__(self, sab_api, name, sensors): + def __init__(self, sab_api): """Initialize component.""" self.sab_api = sab_api - self.name = name - self.sensors = sensors async def async_pause_queue(self): """Pause Sabnzbd queue.""" diff --git a/homeassistant/components/sabnzbd/config_flow.py b/homeassistant/components/sabnzbd/config_flow.py new file mode 100644 index 00000000000..7930363b2ac --- /dev/null +++ b/homeassistant/components/sabnzbd/config_flow.py @@ -0,0 +1,77 @@ +"""Adds config flow for SabNzbd.""" +from __future__ import annotations + +import logging +from typing import Any + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_NAME, + CONF_PATH, + CONF_PORT, + CONF_SSL, + CONF_URL, +) +from homeassistant.data_entry_flow import FlowResult + +from .const import DEFAULT_NAME, DOMAIN +from .sab import get_client + +_LOGGER = logging.getLogger(__name__) + +USER_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): str, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + vol.Required(CONF_URL): str, + vol.Optional(CONF_PATH): str, + } +) + + +class SABnzbdConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Sabnzbd config flow.""" + + VERSION = 1 + + async def _async_validate_input(self, user_input): + """Validate the user input allows us to connect.""" + errors = {} + sab_api = await get_client(self.hass, user_input) + if not sab_api: + errors["base"] = "cannot_connect" + + return errors + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + + errors = {} + if user_input is not None: + + errors = await self._async_validate_input(user_input) + + if not errors: + return self.async_create_entry( + title=user_input[CONF_API_KEY][:12], data=user_input + ) + + return self.async_show_form( + step_id="user", + data_schema=USER_SCHEMA, + errors=errors, + ) + + async def async_step_import(self, import_data): + """Import sabnzbd config from configuration.yaml.""" + protocol = "https://" if import_data[CONF_SSL] else "http://" + import_data[ + CONF_URL + ] = f"{protocol}{import_data[CONF_HOST]}:{import_data[CONF_PORT]}" + return await self.async_step_user(import_data) diff --git a/homeassistant/components/sabnzbd/const.py b/homeassistant/components/sabnzbd/const.py new file mode 100644 index 00000000000..8add1f61493 --- /dev/null +++ b/homeassistant/components/sabnzbd/const.py @@ -0,0 +1,26 @@ +"""Constants for the Sabnzbd component.""" +from datetime import timedelta + +DOMAIN = "sabnzbd" +DATA_SABNZBD = "sabnzbd" + +ATTR_SPEED = "speed" +ATTR_API_KEY = "api_key" + +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "SABnzbd" +DEFAULT_PORT = 8080 +DEFAULT_SPEED_LIMIT = "100" +DEFAULT_SSL = False + +UPDATE_INTERVAL = timedelta(seconds=30) + +SERVICE_PAUSE = "pause" +SERVICE_RESUME = "resume" +SERVICE_SET_SPEED = "set_speed" + +SIGNAL_SABNZBD_UPDATED = "sabnzbd_updated" + +KEY_API = "api" +KEY_API_DATA = "api_data" +KEY_NAME = "name" diff --git a/homeassistant/components/sabnzbd/manifest.json b/homeassistant/components/sabnzbd/manifest.json index f6cbd958206..0702446d217 100644 --- a/homeassistant/components/sabnzbd/manifest.json +++ b/homeassistant/components/sabnzbd/manifest.json @@ -5,7 +5,8 @@ "requirements": ["pysabnzbd==1.1.1"], "dependencies": ["configurator"], "after_dependencies": ["discovery"], - "codeowners": [], + "codeowners": ["@shaiu"], "iot_class": "local_polling", + "config_flow": true, "loggers": ["pysabnzbd"] } diff --git a/homeassistant/components/sabnzbd/sab.py b/homeassistant/components/sabnzbd/sab.py new file mode 100644 index 00000000000..ab3575c7092 --- /dev/null +++ b/homeassistant/components/sabnzbd/sab.py @@ -0,0 +1,27 @@ +"""Support for the Sabnzbd service.""" +from pysabnzbd import SabnzbdApi, SabnzbdApiException + +from homeassistant.const import CONF_API_KEY, CONF_PATH, CONF_URL +from homeassistant.core import _LOGGER, HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + + +async def get_client(hass: HomeAssistant, data): + """Get Sabnzbd client.""" + web_root = data.get(CONF_PATH) + api_key = data[CONF_API_KEY] + url = data[CONF_URL] + + sab_api = SabnzbdApi( + url, + api_key, + web_root=web_root, + session=async_get_clientsession(hass, False), + ) + try: + await sab_api.check_available() + except SabnzbdApiException as exception: + _LOGGER.error("Connection to SABnzbd API failed: %s", exception.message) + return False + + return sab_api diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index 6e8fafdfde1..1d661d90848 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -1,39 +1,119 @@ """Support for monitoring an SABnzbd NZB client.""" from __future__ import annotations -from homeassistant.components.sensor import SensorEntity -from homeassistant.core import HomeAssistant -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from dataclasses import dataclass -from . import ( - DATA_SABNZBD, - SENSOR_TYPES, - SIGNAL_SABNZBD_UPDATED, - SabnzbdSensorEntityDescription, +from homeassistant.components.sensor import ( + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import DOMAIN, SIGNAL_SABNZBD_UPDATED +from ...config_entries import ConfigEntry +from ...const import DATA_GIGABYTES, DATA_MEGABYTES, DATA_RATE_MEGABYTES_PER_SECOND +from ...core import HomeAssistant +from ...helpers.entity_platform import AddEntitiesCallback +from .const import KEY_API_DATA, KEY_NAME + + +@dataclass +class SabnzbdRequiredKeysMixin: + """Mixin for required keys.""" + + key: str + + +@dataclass +class SabnzbdSensorEntityDescription(SensorEntityDescription, SabnzbdRequiredKeysMixin): + """Describes Sabnzbd sensor entity.""" + + +SENSOR_TYPES: tuple[SabnzbdSensorEntityDescription, ...] = ( + SabnzbdSensorEntityDescription( + key="status", + name="Status", + ), + SabnzbdSensorEntityDescription( + key="kbpersec", + name="Speed", + native_unit_of_measurement=DATA_RATE_MEGABYTES_PER_SECOND, + state_class=SensorStateClass.MEASUREMENT, + ), + SabnzbdSensorEntityDescription( + key="mb", + name="Queue", + native_unit_of_measurement=DATA_MEGABYTES, + state_class=SensorStateClass.MEASUREMENT, + ), + SabnzbdSensorEntityDescription( + key="mbleft", + name="Left", + native_unit_of_measurement=DATA_MEGABYTES, + state_class=SensorStateClass.MEASUREMENT, + ), + SabnzbdSensorEntityDescription( + key="diskspacetotal1", + name="Disk", + native_unit_of_measurement=DATA_GIGABYTES, + state_class=SensorStateClass.MEASUREMENT, + ), + SabnzbdSensorEntityDescription( + key="diskspace1", + name="Disk Free", + native_unit_of_measurement=DATA_GIGABYTES, + state_class=SensorStateClass.MEASUREMENT, + ), + SabnzbdSensorEntityDescription( + key="noofslots_total", + name="Queue Count", + state_class=SensorStateClass.TOTAL, + ), + SabnzbdSensorEntityDescription( + key="day_size", + name="Daily Total", + native_unit_of_measurement=DATA_GIGABYTES, + entity_registry_enabled_default=False, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SabnzbdSensorEntityDescription( + key="week_size", + name="Weekly Total", + native_unit_of_measurement=DATA_GIGABYTES, + entity_registry_enabled_default=False, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SabnzbdSensorEntityDescription( + key="month_size", + name="Monthly Total", + native_unit_of_measurement=DATA_GIGABYTES, + entity_registry_enabled_default=False, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + SabnzbdSensorEntityDescription( + key="total_size", + name="Total", + native_unit_of_measurement=DATA_GIGABYTES, + state_class=SensorStateClass.TOTAL_INCREASING, + ), ) +SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES] -async def async_setup_platform( + +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, + config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the SABnzbd sensors.""" - if discovery_info is None: - return + """Set up a Sabnzbd sensor entry.""" + + sab_api_data = hass.data[DOMAIN][config_entry.entry_id][KEY_API_DATA] + client_name = hass.data[DOMAIN][config_entry.entry_id][KEY_NAME] - sab_api_data = hass.data[DATA_SABNZBD] - sensors = sab_api_data.sensors - client_name = sab_api_data.name async_add_entities( - [ - SabnzbdSensor(sab_api_data, client_name, description) - for description in SENSOR_TYPES - if description.key in sensors - ] + [SabnzbdSensor(sab_api_data, client_name, sensor) for sensor in SENSOR_TYPES] ) @@ -62,7 +142,7 @@ class SabnzbdSensor(SensorEntity): def update_state(self, args): """Get the latest data and updates the states.""" self._attr_native_value = self._sabnzbd_api.get_queue_field( - self.entity_description.field_name + self.entity_description.key ) if self.entity_description.key == "speed": diff --git a/homeassistant/components/sabnzbd/services.yaml b/homeassistant/components/sabnzbd/services.yaml index 38f68bfe5dd..2221eed169f 100644 --- a/homeassistant/components/sabnzbd/services.yaml +++ b/homeassistant/components/sabnzbd/services.yaml @@ -1,13 +1,33 @@ pause: name: Pause description: Pauses downloads. + fields: + api_key: + name: Sabnzbd API key + description: The Sabnzbd API key to pause downloads + required: true + selector: + text: resume: name: Resume description: Resumes downloads. + fields: + api_key: + name: Sabnzbd API key + description: The Sabnzbd API key to resume downloads + required: true + selector: + text: set_speed: name: Set speed description: Sets the download speed limit. fields: + api_key: + name: Sabnzbd API key + description: The Sabnzbd API key to set speed limit + required: true + selector: + text: speed: name: Speed description: Speed limit. If specified as a number with no units, will be interpreted as a percent. If units are provided (e.g., 500K) will be interpreted absolutely. diff --git a/homeassistant/components/sabnzbd/strings.json b/homeassistant/components/sabnzbd/strings.json new file mode 100644 index 00000000000..9de5e08230c --- /dev/null +++ b/homeassistant/components/sabnzbd/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "name": "[%key:common::config_flow::data::name%]", + "url": "[%key:common::config_flow::data::url%]", + "path": "[%key:common::config_flow::data::path%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]" + } + } +} diff --git a/homeassistant/components/sabnzbd/translations/de.json b/homeassistant/components/sabnzbd/translations/de.json new file mode 100644 index 00000000000..9f128f2878f --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "name": "Name", + "path": "Pfad", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/el.json b/homeassistant/components/sabnzbd/translations/el.json new file mode 100644 index 00000000000..f3d461e3dee --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/el.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API" + }, + "step": { + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/en.json b/homeassistant/components/sabnzbd/translations/en.json new file mode 100644 index 00000000000..4e857c42b64 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Failed to connect", + "invalid_api_key": "Invalid API key" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "name": "Name", + "path": "Path", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/et.json b/homeassistant/components/sabnzbd/translations/et.json new file mode 100644 index 00000000000..b940ab99569 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/et.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_api_key": "Kehtetu API v\u00f5ti" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "name": "Nimi", + "path": "Rada", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/fr.json b/homeassistant/components/sabnzbd/translations/fr.json new file mode 100644 index 00000000000..9809ccf8a0b --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_api_key": "Cl\u00e9 d'API non valide" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "name": "Nom", + "path": "Chemin d'acc\u00e8s", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/hu.json b/homeassistant/components/sabnzbd/translations/hu.json new file mode 100644 index 00000000000..0136c11fc88 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "name": "Elnevez\u00e9s", + "path": "\u00datvonal", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/id.json b/homeassistant/components/sabnzbd/translations/id.json new file mode 100644 index 00000000000..caa7e73815e --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "name": "Nama", + "path": "Jalur", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/it.json b/homeassistant/components/sabnzbd/translations/it.json new file mode 100644 index 00000000000..48f2dea100c --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/it.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_api_key": "Chiave API non valida" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "name": "Nome", + "path": "Percorso", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/nl.json b/homeassistant/components/sabnzbd/translations/nl.json new file mode 100644 index 00000000000..c737cd0d07e --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_api_key": "Ongeldige API-sleutel" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "name": "Naam", + "path": "Pad", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/no.json b/homeassistant/components/sabnzbd/translations/no.json new file mode 100644 index 00000000000..4da8a925a29 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_api_key": "Ugyldig API-n\u00f8kkel" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "name": "Navn", + "path": "Sti", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/pl.json b/homeassistant/components/sabnzbd/translations/pl.json new file mode 100644 index 00000000000..b083df2b8dc --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_api_key": "Nieprawid\u0142owy klucz API" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "name": "Nazwa", + "path": "\u015acie\u017cka", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/pt-BR.json b/homeassistant/components/sabnzbd/translations/pt-BR.json new file mode 100644 index 00000000000..b9015b40b14 --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/pt-BR.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "name": "Nome", + "path": "Caminho", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sabnzbd/translations/zh-Hant.json b/homeassistant/components/sabnzbd/translations/zh-Hant.json new file mode 100644 index 00000000000..018952ba66c --- /dev/null +++ b/homeassistant/components/sabnzbd/translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_api_key": "API \u91d1\u9470\u7121\u6548" + }, + "step": { + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "name": "\u540d\u7a31", + "path": "\u8def\u5f91", + "url": "\u7db2\u5740" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 2fb3729d0a8..818ce7ea57a 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -209,14 +209,11 @@ class SAJsensor(SensorEntity): @property def device_class(self): """Return the device class the sensor belongs to.""" - if self.unit_of_measurement == POWER_WATT: + if self.native_unit_of_measurement == POWER_WATT: return SensorDeviceClass.POWER - if self.unit_of_measurement == ENERGY_KILO_WATT_HOUR: + if self.native_unit_of_measurement == ENERGY_KILO_WATT_HOUR: return SensorDeviceClass.ENERGY - if ( - self.unit_of_measurement == TEMP_CELSIUS - or self._sensor.unit == TEMP_FAHRENHEIT - ): + if self.native_unit_of_measurement in (TEMP_CELSIUS, TEMP_FAHRENHEIT): return SensorDeviceClass.TEMPERATURE @property diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index b15505d58c8..9391fe5e311 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -7,7 +7,7 @@ "samsungctl[websocket]==0.7.1", "samsungtvws[async,encrypted]==2.5.0", "wakeonlan==2.0.1", - "async-upnp-client==0.27.0" + "async-upnp-client==0.29.0" ], "ssdp": [ { diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 7ab12d1a76a..a0b70af2db5 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -24,21 +24,11 @@ from wakeonlan import send_magic_packet from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.components.media_player.const import ( MEDIA_TYPE_APP, MEDIA_TYPE_CHANNEL, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.const import ( @@ -72,15 +62,15 @@ from .const import ( SOURCES = {"TV": "KEY_TV", "HDMI": "KEY_HDMI"} SUPPORT_SAMSUNGTV = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_NEXT_TRACK - | SUPPORT_TURN_OFF - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA ) # Since the TV will take a few seconds to go to sleep @@ -145,9 +135,9 @@ class SamsungTVDevice(MediaPlayerEntity): self._attr_supported_features = SUPPORT_SAMSUNGTV if self._on_script or self._mac: # Add turn-on if on_script or mac is available - self._attr_supported_features |= SUPPORT_TURN_ON + self._attr_supported_features |= MediaPlayerEntityFeature.TURN_ON if self._ssdp_rendering_control_location: - self._attr_supported_features |= SUPPORT_VOLUME_SET + self._attr_supported_features |= MediaPlayerEntityFeature.VOLUME_SET self._attr_device_info = DeviceInfo( name=self.name, @@ -268,9 +258,7 @@ class SamsungTVDevice(MediaPlayerEntity): except asyncio.TimeoutError as err: # No need to try again self._app_list_event.set() - LOGGER.debug( - "Failed to load app list from %s: %s", self._host, err.__repr__() - ) + LOGGER.debug("Failed to load app list from %s: %r", self._host, err) async def _async_startup_dmr(self) -> None: assert self._ssdp_rendering_control_location is not None @@ -388,9 +376,7 @@ class SamsungTVDevice(MediaPlayerEntity): try: await dmr_device.async_set_volume_level(volume) except UpnpActionResponseError as err: - LOGGER.warning( - "Unable to set volume level on %s: %s", self._host, err.__repr__() - ) + LOGGER.warning("Unable to set volume level on %s: %r", self._host, err) async def async_volume_up(self) -> None: """Volume up the media player.""" diff --git a/homeassistant/components/samsungtv/translations/bg.json b/homeassistant/components/samsungtv/translations/bg.json index 332b6b62236..2246b4ad954 100644 --- a/homeassistant/components/samsungtv/translations/bg.json +++ b/homeassistant/components/samsungtv/translations/bg.json @@ -6,11 +6,17 @@ "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e", "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, + "error": { + "invalid_pin": "\u041f\u0418\u041d \u043a\u043e\u0434\u044a\u0442 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d, \u043c\u043e\u043b\u044f, \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e." + }, "flow_title": "{device}", "step": { "confirm": { "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u041f\u0418\u041d \u043a\u043e\u0434\u0430, \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u043d\u0430 {device}." + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/samsungtv/translations/ca.json b/homeassistant/components/samsungtv/translations/ca.json index e701bdb1d92..95738ae65d9 100644 --- a/homeassistant/components/samsungtv/translations/ca.json +++ b/homeassistant/components/samsungtv/translations/ca.json @@ -12,7 +12,8 @@ "unknown": "Error inesperat" }, "error": { - "auth_missing": "Home Assistant no est\u00e0 autenticat per connectar-se amb aquest televisor Samsung. V\u00e9s a la configuraci\u00f3 de dispositius externs del televisor per autoritzar Home Assistant." + "auth_missing": "Home Assistant no est\u00e0 autenticat per connectar-se amb aquest televisor Samsung. V\u00e9s a la configuraci\u00f3 de dispositius externs del televisor per autoritzar Home Assistant.", + "invalid_pin": "El PIN \u00e9s inv\u00e0lid; torna-ho a provar." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Vols configurar {device}? Si mai abans has connectat Home Assistant hauries de veure una finestra emergent a la pantalla del televisor demanant autenticaci\u00f3.", "title": "Televisor Samsung" }, + "encrypted_pairing": { + "description": "Introdueix el PIN que es mostra a {device}." + }, + "pairing": { + "description": "Vols configurar {device}? Si mai abans has connectat Home Assistant hauries de veure una finestra emergent a la pantalla del televisor demanant autenticaci\u00f3." + }, "reauth_confirm": { - "description": "Despr\u00e9s d'enviar, tens 30 segons per acceptar la finestra emergent de {device} que sol\u00b7licita autoritzaci\u00f3." + "description": "Despr\u00e9s d'enviar, tens 30 segons per acceptar o introduir el PIN a la finestra emergent de {device} que sol\u00b7licita autoritzaci\u00f3." + }, + "reauth_confirm_encrypted": { + "description": "Introdueix el PIN que es mostra a {device}." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/cs.json b/homeassistant/components/samsungtv/translations/cs.json index d45c9247b4e..4c2241d4caa 100644 --- a/homeassistant/components/samsungtv/translations/cs.json +++ b/homeassistant/components/samsungtv/translations/cs.json @@ -19,6 +19,15 @@ "description": "Chcete nastavit {device}? Pokud jste Home Assistant doposud nikdy nep\u0159ipojili, m\u011bla by se v\u00e1m na televizi zobrazit \u017e\u00e1dost o povolen\u00ed.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "Zadejte pros\u00edm PIN zobrazen\u00fd na {device}." + }, + "pairing": { + "description": "Chcete nastavit {device}? Pokud jste Home Assistant doposud nikdy nep\u0159ipojili, m\u011bla by se v\u00e1m na televizi zobrazit \u017e\u00e1dost o povolen\u00ed." + }, + "reauth_confirm_encrypted": { + "description": "Zadejte pros\u00edm PIN zobrazen\u00fd na {device}." + }, "user": { "data": { "host": "Hostitel", diff --git a/homeassistant/components/samsungtv/translations/de.json b/homeassistant/components/samsungtv/translations/de.json index ec5b791626a..bfacbcd4b3c 100644 --- a/homeassistant/components/samsungtv/translations/de.json +++ b/homeassistant/components/samsungtv/translations/de.json @@ -12,7 +12,8 @@ "unknown": "Unerwarteter Fehler" }, "error": { - "auth_missing": "Home Assistant ist nicht berechtigt, eine Verbindung zu diesem Samsung TV herzustellen. \u00dcberpr\u00fcfe den Ger\u00e4teverbindungsmanager in den Einstellungen deines Fernsehger\u00e4ts, um Home Assistant zu autorisieren." + "auth_missing": "Home Assistant ist nicht berechtigt, eine Verbindung zu diesem Samsung TV herzustellen. \u00dcberpr\u00fcfe den Ger\u00e4teverbindungsmanager in den Einstellungen deines Fernsehger\u00e4ts, um Home Assistant zu autorisieren.", + "invalid_pin": "PIN ist ung\u00fcltig, bitte versuche es erneut." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "M\u00f6chtest du Samsung TV {device} einrichten? Wenn du noch nie eine Verbindung zum Home Assistant hergestellt hast, solltest du eine Meldung auf deinem Fernseher sehen, die nach einer Autorisierung fragt. Manuelle Konfigurationen f\u00fcr dieses Fernsehger\u00e4t werden \u00fcberschrieben.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "Bitte gib die PIN ein, die auf {device} angezeigt wird." + }, + "pairing": { + "description": "M\u00f6chtest du Samsung TV {device} einrichten? Wenn du noch nie eine Verbindung zum Home Assistant hergestellt hast, solltest du eine Meldung auf deinem Fernseher sehen, die nach einer Autorisierung fragt. Manuelle Konfigurationen f\u00fcr dieses Fernsehger\u00e4t werden \u00fcberschrieben." + }, "reauth_confirm": { - "description": "Akzeptiere nach dem Absenden die Meldung auf {device}, das eine Autorisierung innerhalb von 30 Sekunden anfordert." + "description": "Akzeptiere nach dem Absenden das Popup-Fenster auf {device}, das dich auffordert, sich innerhalb von 30 Sekunden zu autorisieren, oder gib die PIN ein." + }, + "reauth_confirm_encrypted": { + "description": "Bitte gib die PIN ein, die auf {device} angezeigt wird." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/el.json b/homeassistant/components/samsungtv/translations/el.json index aa2ec61978b..0e6c4df03bc 100644 --- a/homeassistant/components/samsungtv/translations/el.json +++ b/homeassistant/components/samsungtv/translations/el.json @@ -12,17 +12,27 @@ "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "error": { - "auth_missing": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Samsung. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant." + "auth_missing": "\u03a4\u03bf Home Assistant \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af \u03c3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 Samsung. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03b7\u03c2 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7\u03c2 \u03b5\u03be\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03ce\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd \u03c4\u03b7\u03c2 \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03bf\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Home Assistant.", + "invalid_pin": "\u03a4\u03bf PIN \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." }, "flow_title": "{device}", "step": { "confirm": { - "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 {device}; \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9 \u03c0\u03bf\u03c4\u03ad \u03c4\u03bf Home Assistant \u03c0\u03c1\u03b9\u03bd, \u03b8\u03b1 \u03b4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7.", + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {device}; \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9 \u03c0\u03bf\u03c4\u03ad \u03c4\u03bf Home Assistant \u03c0\u03c1\u03b9\u03bd, \u03b8\u03b1 \u03b4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7 {device}." + }, + "pairing": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {device}; \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9 \u03c0\u03bf\u03c4\u03ad \u03c4\u03bf Home Assistant \u03c0\u03c1\u03b9\u03bd, \u03b8\u03b1 \u03b4\u03b5\u03af\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c0\u03bf\u03c5 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7." + }, "reauth_confirm": { "description": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae, \u03b1\u03c0\u03bf\u03b4\u03b5\u03c7\u03c4\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b4\u03c5\u03cc\u03bc\u03b5\u03bd\u03bf \u03c0\u03b1\u03c1\u03ac\u03b8\u03c5\u03c1\u03bf \u03c3\u03c4\u03b7 {device} \u03c0\u03bf\u03c5 \u03b6\u03b7\u03c4\u03ac \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7 \u03b5\u03bd\u03c4\u03cc\u03c2 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03bf\u03bb\u03ad\u03c0\u03c4\u03c9\u03bd." }, + "reauth_confirm_encrypted": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf PIN \u03c0\u03bf\u03c5 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7 {device}." + }, "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", diff --git a/homeassistant/components/samsungtv/translations/en.json b/homeassistant/components/samsungtv/translations/en.json index c4e0e181090..827f9992e46 100644 --- a/homeassistant/components/samsungtv/translations/en.json +++ b/homeassistant/components/samsungtv/translations/en.json @@ -6,6 +6,7 @@ "auth_missing": "Home Assistant is not authorized to connect to this Samsung TV. Check your TV's External Device Manager settings to authorize Home Assistant.", "cannot_connect": "Failed to connect", "id_missing": "This Samsung device doesn't have a SerialNumber.", + "missing_config_entry": "This Samsung device doesn't have a configuration entry.", "not_supported": "This Samsung device is currently not supported.", "reauth_successful": "Re-authentication was successful", "unknown": "Unexpected error" @@ -17,7 +18,8 @@ "flow_title": "{device}", "step": { "confirm": { - "description": "Do you want to set up {device}? If you never connected Home Assistant before you should see a popup on your TV asking for authorization." + "description": "Do you want to set up {device}? If you never connected Home Assistant before you should see a popup on your TV asking for authorization.", + "title": "Samsung TV" }, "encrypted_pairing": { "description": "Please enter the PIN displayed on {device}." diff --git a/homeassistant/components/samsungtv/translations/et.json b/homeassistant/components/samsungtv/translations/et.json index 47360f4ed06..94e9db78ec2 100644 --- a/homeassistant/components/samsungtv/translations/et.json +++ b/homeassistant/components/samsungtv/translations/et.json @@ -12,7 +12,8 @@ "unknown": "Tundmatu t\u00f5rge" }, "error": { - "auth_missing": "Tuvastamine nurjus" + "auth_missing": "Tuvastamine nurjus", + "invalid_pin": "PIN on kehtetu, proovi uuesti." }, "flow_title": "{devicel}", "step": { @@ -20,8 +21,17 @@ "description": "Kas soovid seadistada {devicel} ? Kui seda pole kunagi enne Home Assistantiga \u00fchendatud, n\u00e4ed oma teleris h\u00fcpikakent, mis k\u00fcsib tuvastamist.", "title": "" }, + "encrypted_pairing": { + "description": "Sisesta seadmes {device} kuvatav PIN-kood." + }, + "pairing": { + "description": "Kas h\u00e4\u00e4lestada seade {device}? Kui varem pole Home Assistantiga \u00fchendutud peaks teler kuvama h\u00fcpikakna tuvastusteabe sisestamiseks." + }, "reauth_confirm": { - "description": "P\u00e4rast esitamist n\u00f5ustu {device} h\u00fcpikaknaga, mis taotleb autoriseerimist 30 sekundi jooksul." + "description": "P\u00e4rast esitamist n\u00f5ustu {device} h\u00fcpikaknaga, mis taotleb autoriseerimist 30 sekundi jooksul v\u00f5i sisesta PIN." + }, + "reauth_confirm_encrypted": { + "description": "Sisesta seadmes {device} kuvatav PIN-kood." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/fr.json b/homeassistant/components/samsungtv/translations/fr.json index 1d9bebb5569..438336f2818 100644 --- a/homeassistant/components/samsungtv/translations/fr.json +++ b/homeassistant/components/samsungtv/translations/fr.json @@ -12,16 +12,26 @@ "unknown": "Erreur inattendue" }, "error": { - "auth_missing": "Home Assistant n'est pas autoris\u00e9 \u00e0 se connecter \u00e0 ce t\u00e9l\u00e9viseur Samsung. Veuillez v\u00e9rifier les param\u00e8tres de votre t\u00e9l\u00e9viseur pour autoriser Home Assistant." + "auth_missing": "Home Assistant n'est pas autoris\u00e9 \u00e0 se connecter \u00e0 ce t\u00e9l\u00e9viseur Samsung. Veuillez v\u00e9rifier les param\u00e8tres de votre t\u00e9l\u00e9viseur pour autoriser Home Assistant.", + "invalid_pin": "Le code PIN n'est pas valide, veuillez r\u00e9essayer." }, "flow_title": "{device}", "step": { "confirm": { - "description": "Voulez vous installer la TV {device} Samsung? Si vous n'avez jamais connect\u00e9 Home Assistant avant, vous devriez voir une fen\u00eatre contextuelle sur votre t\u00e9l\u00e9viseur demandant une authentification. Les configurations manuelles de ce t\u00e9l\u00e9viseur seront \u00e9cras\u00e9es.", + "description": "Voulez-vous configurer {device}\u00a0? Si vous n'avez jamais connect\u00e9 Home Assistant auparavant, une fen\u00eatre contextuelle d'autorisation devrait appara\u00eetre sur votre t\u00e9l\u00e9viseur.", "title": "TV Samsung" }, + "encrypted_pairing": { + "description": "Veuillez saisir le code PIN affich\u00e9 sur {device}." + }, + "pairing": { + "description": "Voulez-vous configurer {device}\u00a0? Si vous n'avez jamais connect\u00e9 Home Assistant auparavant, une fen\u00eatre contextuelle d'autorisation devrait appara\u00eetre sur votre t\u00e9l\u00e9viseur." + }, "reauth_confirm": { - "description": "Apr\u00e8s avoir soumis, acceptez la fen\u00eatre contextuelle sur {device} demandant l'autorisation dans les 30 secondes." + "description": "Une fois envoy\u00e9, acceptez la fen\u00eatre contextuelle de demande d'autorisation sur {device} dans les 30\u00a0secondes ou saisissez le code PIN." + }, + "reauth_confirm_encrypted": { + "description": "Veuillez saisir le code PIN affich\u00e9 sur {device}." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index a85f295317d..dc42596e290 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "auth_missing": "Home Assistant nem jogosult csatlakozni ehhez a Samsung TV-hez. Ellen\u0151rizze a TV be\u00e1ll\u00edt\u00e1sait Home Assistant enged\u00e9lyez\u00e9s\u00e9hez.", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "id_missing": "Ennek a Samsung eszk\u00f6znek nincs sorsz\u00e1ma.", @@ -12,7 +12,8 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { - "auth_missing": "Home Assistant nem jogosult csatlakozni ehhez a Samsung TV-hez. Ellen\u0151rizze a TV be\u00e1ll\u00edt\u00e1sait Home Assistant enged\u00e9lyez\u00e9s\u00e9hez." + "auth_missing": "Home Assistant nem jogosult csatlakozni ehhez a Samsung TV-hez. Ellen\u0151rizze a TV be\u00e1ll\u00edt\u00e1sait Home Assistant enged\u00e9lyez\u00e9s\u00e9hez.", + "invalid_pin": "A PIN k\u00f3d \u00e9rv\u00e9nytelen, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg \u00fajra." }, "flow_title": "{device}", "step": { @@ -20,13 +21,22 @@ "description": "Szeretn\u00e9 be\u00e1ll\u00edtani {device} k\u00e9sz\u00fcl\u00e9k\u00e9t? Ha kor\u00e1bban m\u00e9g sosem csatlakoztatta Home Assistanthoz, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ami j\u00f3v\u00e1hagy\u00e1sra v\u00e1r.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "K\u00e9rj\u00fck, adja meg az eszk\u00f6z\u00f6n megjelen\u0151 PIN-k\u00f3dot: {device}" + }, + "pairing": { + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani {device} k\u00e9sz\u00fcl\u00e9k\u00e9t? Ha kor\u00e1bban m\u00e9g sosem csatlakoztatta Home Assistanthoz, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ami j\u00f3v\u00e1hagy\u00e1sra v\u00e1r." + }, "reauth_confirm": { - "description": "A bek\u00fcld\u00e9s ut\u00e1n, fogadja el a {device} felugr\u00f3 ablak\u00e1ban l\u00e1that\u00f3 \u00fczenetet, mely 30 m\u00e1sodpercig \u00e1ll rendelkez\u00e9sre." + "description": "A bek\u00fcld\u00e9s ut\u00e1n, fogadja el a {device} felugr\u00f3 ablak\u00e1ban l\u00e1that\u00f3 \u00fczenetet, mely 30 m\u00e1sodpercig \u00e1ll rendelkez\u00e9sre, vagy adja meg a PIN k\u00f3dot." + }, + "reauth_confirm_encrypted": { + "description": "K\u00e9rj\u00fck, adja meg az eszk\u00f6z\u00f6n megjelen\u0151 PIN-k\u00f3dot: {device}" }, "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "\u00cdrja be a Samsung TV adatait. Ha m\u00e9g soha nem csatlakozott Home Assistanthoz, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ahol meg kell adni az enged\u00e9lyt." } diff --git a/homeassistant/components/samsungtv/translations/id.json b/homeassistant/components/samsungtv/translations/id.json index 0714af37146..394cfc41ae1 100644 --- a/homeassistant/components/samsungtv/translations/id.json +++ b/homeassistant/components/samsungtv/translations/id.json @@ -12,7 +12,8 @@ "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "auth_missing": "Home Assistant tidak diizinkan untuk tersambung ke TV Samsung ini. Periksa Manajer Perangkat Eksternal TV Anda untuk mengotorisasi Home Assistant." + "auth_missing": "Home Assistant tidak diizinkan untuk tersambung ke TV Samsung ini. Periksa Manajer Perangkat Eksternal TV Anda untuk mengotorisasi Home Assistant.", + "invalid_pin": "PIN tidak valid, silakan coba lagi." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Apakah Anda ingin menyiapkan {device}? Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi.", "title": "TV Samsung" }, + "encrypted_pairing": { + "description": "Masukkan PIN yang ditampilkan di {device} ." + }, + "pairing": { + "description": "Apakah Anda ingin menyiapkan {device}? Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi." + }, "reauth_confirm": { - "description": "Setelah mengirimkan, setujui pada popup di {device} yang meminta otorisasi dalam waktu 30 detik." + "description": "Setelah mengirimkan, setujui pada popup di {device} yang meminta otorisasi dalam waktu 30 detik atau masukkan PIN." + }, + "reauth_confirm_encrypted": { + "description": "Masukkan PIN yang ditampilkan di {device} ." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/it.json b/homeassistant/components/samsungtv/translations/it.json index 51f9b4e2ef9..6bfc26fb445 100644 --- a/homeassistant/components/samsungtv/translations/it.json +++ b/homeassistant/components/samsungtv/translations/it.json @@ -12,7 +12,8 @@ "unknown": "Errore imprevisto" }, "error": { - "auth_missing": "Home Assistant non \u00e8 autorizzato a connettersi a questo televisore Samsung. Controlla le impostazioni di Gestione dispositivi esterni della tua TV per autorizzare Home Assistant." + "auth_missing": "Home Assistant non \u00e8 autorizzato a connettersi a questo televisore Samsung. Controlla le impostazioni di Gestione dispositivi esterni della tua TV per autorizzare Home Assistant.", + "invalid_pin": "Il PIN non \u00e8 valido, riprova." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Vuoi configurare {device}? Se non hai mai collegato Home Assistant, dovresti vedere un popup sulla tua TV che chiede l'autorizzazione.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "Digita il PIN visualizzato su {device}." + }, + "pairing": { + "description": "Vuoi configurare {device}? Se non hai mai collegato Home Assistant, dovresti vedere un popup sulla tua TV che chiede l'autorizzazione." + }, "reauth_confirm": { - "description": "Dopo l'invio, accetta il popup su {device} richiedendo l'autorizzazione entro 30 secondi." + "description": "Dopo l'invio, accetta la notifica su {device} richiedendo l'autorizzazione entro 30 secondi o digita il PIN." + }, + "reauth_confirm_encrypted": { + "description": "Digita il PIN visualizzato su {device}." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/ja.json b/homeassistant/components/samsungtv/translations/ja.json index 0cfad60efcb..d1b5670e25c 100644 --- a/homeassistant/components/samsungtv/translations/ja.json +++ b/homeassistant/components/samsungtv/translations/ja.json @@ -12,7 +12,8 @@ "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "error": { - "auth_missing": "Home Assistant\u306f\u3001\u3053\u306eSamsungTV\u3078\u306e\u63a5\u7d9a\u3092\u8a31\u53ef\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c6\u30ec\u30d3\u306e\u5916\u90e8\u30c7\u30d0\u30a4\u30b9\u30de\u30cd\u30fc\u30b8\u30e3\u30fc\u306e\u8a2d\u5b9a\u3092\u78ba\u8a8d\u3057\u3066\u3001Home Assistant\u3092\u8a8d\u8a3c\u3057\u307e\u3059\u3002" + "auth_missing": "Home Assistant\u306f\u3001\u3053\u306eSamsungTV\u3078\u306e\u63a5\u7d9a\u3092\u8a31\u53ef\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c6\u30ec\u30d3\u306e\u5916\u90e8\u30c7\u30d0\u30a4\u30b9\u30de\u30cd\u30fc\u30b8\u30e3\u30fc\u306e\u8a2d\u5b9a\u3092\u78ba\u8a8d\u3057\u3066\u3001Home Assistant\u3092\u8a8d\u8a3c\u3057\u307e\u3059\u3002", + "invalid_pin": "PIN\u304c\u7121\u52b9\u3067\u3059\u3002\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002" }, "flow_title": "{device}", "step": { @@ -20,9 +21,18 @@ "description": "{device} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f\u3053\u308c\u307e\u3067\u306bHome Assistant\u3092\u4e00\u5ea6\u3082\u63a5\u7d9a\u3057\u305f\u3053\u3068\u304c\u306a\u3044\u5834\u5408\u306f\u3001\u30c6\u30ec\u30d3\u306b\u8a8d\u8a3c\u3092\u6c42\u3081\u308b\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7\u304c\u8868\u793a\u3055\u308c\u307e\u3059\u3002", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "{device} \u306b\u8868\u793a\u3055\u308c\u3066\u3044\u308bPIN\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, + "pairing": { + "description": "{device} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f\u3053\u308c\u307e\u3067\u306bHome Assistant\u3092\u4e00\u5ea6\u3082\u63a5\u7d9a\u3057\u305f\u3053\u3068\u304c\u306a\u3044\u5834\u5408\u306f\u3001\u30c6\u30ec\u30d3\u306b\u8a8d\u8a3c\u3092\u6c42\u3081\u308b\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7\u304c\u8868\u793a\u3055\u308c\u307e\u3059\u3002" + }, "reauth_confirm": { "description": "\u9001\u4fe1(submit)\u3001\u8a8d\u8a3c\u3092\u8981\u6c42\u3059\u308b {device} \u306e\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7\u3092\u300130\u79d2\u4ee5\u5185\u306b\u53d7\u3051\u5165\u308c\u307e\u3059\u3002" }, + "reauth_confirm_encrypted": { + "description": "{device} \u306b\u8868\u793a\u3055\u308c\u3066\u3044\u308bPIN\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + }, "user": { "data": { "host": "\u30db\u30b9\u30c8", diff --git a/homeassistant/components/samsungtv/translations/nl.json b/homeassistant/components/samsungtv/translations/nl.json index 692c70642d6..b75f9b5970e 100644 --- a/homeassistant/components/samsungtv/translations/nl.json +++ b/homeassistant/components/samsungtv/translations/nl.json @@ -12,7 +12,8 @@ "unknown": "Onverwachte fout" }, "error": { - "auth_missing": "Home Assistant is niet gemachtigd om verbinding te maken met deze Samsung TV. Controleer de instellingen van Extern apparaatbeheer van uw tv om Home Assistant te machtigen." + "auth_missing": "Home Assistant is niet gemachtigd om verbinding te maken met deze Samsung TV. Controleer de instellingen van Extern apparaatbeheer van uw tv om Home Assistant te machtigen.", + "invalid_pin": "Pincode is ongeldig, probeer het opnieuw." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Wilt u Samsung TV {device} instellen? Als u nooit eerder Home Assistant hebt verbonden dan zou u een popup op uw TV moeten zien waarin u om toestemming wordt vraagt. Handmatige configuraties voor deze TV worden overschreven", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "Voer de pincode in die wordt weergegeven op {device} ." + }, + "pairing": { + "description": "Wil je {device} instellen? Als je de Home Assistant nog nooit eerder hebt aangesloten, zou je een pop-up op je tv moeten zien die om autorisatie vraagt." + }, "reauth_confirm": { - "description": "Na het indienen, accepteer binnen 30 seconden de pop-up op {device} om autorisatie toe te staan." + "description": "Na het indienen, accepteer binnen 30 seconden de pop-up op {device} om autorisatie toe te staan of voer PIN in." + }, + "reauth_confirm_encrypted": { + "description": "Voer de pincode in die wordt weergegeven op {device} ." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/no.json b/homeassistant/components/samsungtv/translations/no.json index 7b7108cbf77..2125b407f29 100644 --- a/homeassistant/components/samsungtv/translations/no.json +++ b/homeassistant/components/samsungtv/translations/no.json @@ -12,7 +12,8 @@ "unknown": "Uventet feil" }, "error": { - "auth_missing": "Home Assistant er ikke autorisert til \u00e5 koble til denne Samsung TV-en. Sjekk TV-ens innstillinger for ekstern enhetsbehandling for \u00e5 autorisere Home Assistant." + "auth_missing": "Home Assistant er ikke autorisert til \u00e5 koble til denne Samsung TV-en. Sjekk TV-ens innstillinger for ekstern enhetsbehandling for \u00e5 autorisere Home Assistant.", + "invalid_pin": "PIN-koden er ugyldig, pr\u00f8v igjen." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Vil du konfigurere {device} ? Hvis du aldri har koblet til Home Assistant f\u00f8r, b\u00f8r du se en popup p\u00e5 TV-en din som ber om autorisasjon.", "title": "" }, + "encrypted_pairing": { + "description": "Vennligst skriv inn PIN-koden som vises p\u00e5 {device} ." + }, + "pairing": { + "description": "Vil du konfigurere {device} ? Hvis du aldri har koblet til Home Assistant f\u00f8r, b\u00f8r du se en popup p\u00e5 TV-en din som ber om autorisasjon." + }, "reauth_confirm": { - "description": "Etter innsending, godta popup-vinduet p\u00e5 {device} ber om autorisasjon innen 30 sekunder." + "description": "Etter innsending, godta popup-vinduet p\u00e5 {device} ber om autorisasjon innen 30 sekunder eller angi PIN-kode." + }, + "reauth_confirm_encrypted": { + "description": "Vennligst skriv inn PIN-koden som vises p\u00e5 {device} ." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/pl.json b/homeassistant/components/samsungtv/translations/pl.json index 1f810248f92..015f048dc60 100644 --- a/homeassistant/components/samsungtv/translations/pl.json +++ b/homeassistant/components/samsungtv/translations/pl.json @@ -12,7 +12,8 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { - "auth_missing": "Home Assistant nie ma uprawnie\u0144 do po\u0142\u0105czenia si\u0119 z tym telewizorem Samsung. Sprawd\u017a ustawienia \"Mened\u017cera urz\u0105dze\u0144 zewn\u0119trznych\", aby autoryzowa\u0107 Home Assistant." + "auth_missing": "Home Assistant nie ma uprawnie\u0144 do po\u0142\u0105czenia si\u0119 z tym telewizorem Samsung. Sprawd\u017a ustawienia \"Mened\u017cera urz\u0105dze\u0144 zewn\u0119trznych\", aby autoryzowa\u0107 Home Assistant.", + "invalid_pin": "Nieprawid\u0142owy kod PIN, spr\u00f3buj ponownie." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Czy chcesz skonfigurowa\u0107 {device}? Je\u015bli nigdy wcze\u015bniej nie \u0142\u0105czy\u0142e\u015b go z Home Assistantem, na jego ekranie powinna pojawi\u0107 si\u0119 pro\u015bba o uwierzytelnienie.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "Wprowad\u017a kod PIN wy\u015bwietlony na {device}." + }, + "pairing": { + "description": "Czy chcesz skonfigurowa\u0107 {device}? Je\u015bli nigdy wcze\u015bniej nie \u0142\u0105czy\u0142e\u015b go z Home Assistantem, na jego ekranie powinna pojawi\u0107 si\u0119 pro\u015bba o uwierzytelnienie." + }, "reauth_confirm": { - "description": "Po wys\u0142aniu \u017c\u0105dania, zaakceptuj wyskakuj\u0105ce okienko na {device} z pro\u015bb\u0105 o autoryzacj\u0119 w ci\u0105gu 30 sekund." + "description": "Po wys\u0142aniu \u017c\u0105dania, zaakceptuj wyskakuj\u0105ce okienko na {device} z pro\u015bb\u0105 o autoryzacj\u0119 w ci\u0105gu 30 sekund lub wprowad\u017a PIN." + }, + "reauth_confirm_encrypted": { + "description": "Wprowad\u017a kod PIN wy\u015bwietlony na {device}." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/pt-BR.json b/homeassistant/components/samsungtv/translations/pt-BR.json index 407e9d94d0a..dbb4b50fe93 100644 --- a/homeassistant/components/samsungtv/translations/pt-BR.json +++ b/homeassistant/components/samsungtv/translations/pt-BR.json @@ -12,7 +12,8 @@ "unknown": "Erro inesperado" }, "error": { - "auth_missing": "O Home Assistant n\u00e3o est\u00e1 autorizado a se conectar a esta TV Samsung. Verifique as configura\u00e7\u00f5es do Gerenciador de dispositivos externos da sua TV para autorizar o Home Assistant." + "auth_missing": "O Home Assistant n\u00e3o est\u00e1 autorizado a se conectar a esta TV Samsung. Verifique as configura\u00e7\u00f5es do Gerenciador de dispositivos externos da sua TV para autorizar o Home Assistant.", + "invalid_pin": "O PIN \u00e9 inv\u00e1lido, tente novamente." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "Deseja configurar {device}? Se voc\u00ea nunca conectou o Home Assistant antes, aparecer\u00e1 um pop-up na sua TV pedindo autoriza\u00e7\u00e3o.", "title": "TV Samsung" }, + "encrypted_pairing": { + "description": "Insira o PIN exibido em {device}." + }, + "pairing": { + "description": "Deseja configurar {device}? Se voc\u00ea nunca conectou o Home Assistant antes, aparecer\u00e1 um pop-up na sua TV pedindo autoriza\u00e7\u00e3o." + }, "reauth_confirm": { - "description": "Ap\u00f3s o envio, aceite o pop-up em {device} solicitando autoriza\u00e7\u00e3o em 30 segundos." + "description": "Ap\u00f3s o envio, aceite o pop-up em {device} solicitando autoriza\u00e7\u00e3o em 30 segundos ou insira o PIN." + }, + "reauth_confirm_encrypted": { + "description": "Insira o PIN exibido em {device}." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/ru.json b/homeassistant/components/samsungtv/translations/ru.json index 111b30c5488..ee73070cf0e 100644 --- a/homeassistant/components/samsungtv/translations/ru.json +++ b/homeassistant/components/samsungtv/translations/ru.json @@ -12,7 +12,8 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { - "auth_missing": "Home Assistant \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Samsung TV. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 External Device Manager \u0412\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430." + "auth_missing": "Home Assistant \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Samsung TV. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 External Device Manager \u0412\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430.", + "invalid_pin": "PIN-\u043a\u043e\u0434 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {device}? \u0415\u0441\u043b\u0438 \u0412\u044b \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u043d\u044c\u0448\u0435 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u043b\u0438 \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043a Home Assistant, \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u043e\u043a\u043d\u043e \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "title": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 Samsung" }, + "encrypted_pairing": { + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u043d\u0430 {device}." + }, + "pairing": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {device}? \u0415\u0441\u043b\u0438 \u0412\u044b \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u043d\u044c\u0448\u0435 \u043d\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u043b\u0438 \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043a Home Assistant, \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u043e\u043a\u043d\u043e \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438." + }, "reauth_confirm": { - "description": "\u041f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u044d\u0442\u043e\u0439 \u0444\u043e\u0440\u043c\u044b, \u043f\u0440\u0438\u043c\u0438\u0442\u0435 \u0437\u0430\u043f\u0440\u043e\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u043e \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u043c \u043e\u043a\u043d\u0435 \u043d\u0430 {device} \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 30 \u0441\u0435\u043a\u0443\u043d\u0434." + "description": "\u041f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u044d\u0442\u043e\u0439 \u0444\u043e\u0440\u043c\u044b, \u043f\u0440\u0438\u043c\u0438\u0442\u0435 \u0437\u0430\u043f\u0440\u043e\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u043e \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u043c \u043e\u043a\u043d\u0435 \u043d\u0430 {device} \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 30 \u0441\u0435\u043a\u0443\u043d\u0434 \u0438\u043b\u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434." + }, + "reauth_confirm_encrypted": { + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u043d\u0430 {device}." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/tr.json b/homeassistant/components/samsungtv/translations/tr.json index 399f135bbe8..668b321e26d 100644 --- a/homeassistant/components/samsungtv/translations/tr.json +++ b/homeassistant/components/samsungtv/translations/tr.json @@ -12,7 +12,8 @@ "unknown": "Beklenmeyen hata" }, "error": { - "auth_missing": "Home Assistant'\u0131n bu Samsung TV'ye ba\u011flanma izni yok. Home Assistant'\u0131 yetkilendirmek i\u00e7in l\u00fctfen TV'nin ayarlar\u0131n\u0131 kontrol et." + "auth_missing": "Home Assistant'\u0131n bu Samsung TV'ye ba\u011flanma izni yok. Home Assistant'\u0131 yetkilendirmek i\u00e7in l\u00fctfen TV'nin ayarlar\u0131n\u0131 kontrol et.", + "invalid_pin": "PIN ge\u00e7ersiz, l\u00fctfen tekrar deneyin." }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "{device} kurulumunu yapmak istiyor musunuz? Home Assistant'\u0131 daha \u00f6nce hi\u00e7 ba\u011flamad\u0131ysan\u0131z, TV'nizde yetki isteyen bir a\u00e7\u0131l\u0131r pencere g\u00f6rmelisiniz.", "title": "Samsung TV" }, + "encrypted_pairing": { + "description": "L\u00fctfen {device} \u00fczerinde g\u00f6r\u00fcnt\u00fclenen PIN'i girin." + }, + "pairing": { + "description": "{device} kurulumunu yapmak istiyor musunuz? Home Assistant'\u0131 daha \u00f6nce hi\u00e7 ba\u011flamad\u0131ysan\u0131z, TV'nizde yetki isteyen bir a\u00e7\u0131l\u0131r pencere g\u00f6rmelisiniz." + }, "reauth_confirm": { - "description": "G\u00f6nderdikten sonra, 30 saniye i\u00e7inde yetkilendirme isteyen {device} \u00fczerindeki a\u00e7\u0131l\u0131r pencereyi kabul edin." + "description": "G\u00f6nderdikten sonra, 30 saniye i\u00e7inde yetkilendirme isteyen {device} \u00fczerindeki a\u00e7\u0131l\u0131r pencereyi kabul edin veya PIN'i girin." + }, + "reauth_confirm_encrypted": { + "description": "L\u00fctfen {device} \u00fczerinde g\u00f6r\u00fcnt\u00fclenen PIN'i girin." }, "user": { "data": { diff --git a/homeassistant/components/samsungtv/translations/zh-Hant.json b/homeassistant/components/samsungtv/translations/zh-Hant.json index ba828665cea..8f30090dd24 100644 --- a/homeassistant/components/samsungtv/translations/zh-Hant.json +++ b/homeassistant/components/samsungtv/translations/zh-Hant.json @@ -12,7 +12,8 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { - "auth_missing": "Home Assistant \u672a\u7372\u5f97\u9a57\u8b49\u4ee5\u9023\u7dda\u81f3\u6b64\u4e09\u661f\u96fb\u8996\u3002\u8acb\u6aa2\u67e5\u60a8\u7684\u96fb\u8996\u5916\u90e8\u88dd\u7f6e\u7ba1\u7406\u54e1\u8a2d\u5b9a\u4ee5\u9032\u884c\u9a57\u8b49\u3002" + "auth_missing": "Home Assistant \u672a\u7372\u5f97\u9a57\u8b49\u4ee5\u9023\u7dda\u81f3\u6b64\u4e09\u661f\u96fb\u8996\u3002\u8acb\u6aa2\u67e5\u60a8\u7684\u96fb\u8996\u5916\u90e8\u88dd\u7f6e\u7ba1\u7406\u54e1\u8a2d\u5b9a\u4ee5\u9032\u884c\u9a57\u8b49\u3002", + "invalid_pin": "PIN \u78bc\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002" }, "flow_title": "{device}", "step": { @@ -20,8 +21,17 @@ "description": "\u662f\u5426\u8981\u8a2d\u5b9a {device}\uff1f\u5047\u5982\u60a8\u4e4b\u524d\u672a\u66fe\u9023\u7dda\u81f3 Home Assistant\uff0c\u61c9\u8a72\u6703\u65bc\u96fb\u8996\u4e0a\u6536\u5230\u9a57\u8b49\u8a0a\u606f\u3002", "title": "\u4e09\u661f\u96fb\u8996" }, + "encrypted_pairing": { + "description": "\u8acb\u8f38\u5165\u986f\u793a\u65bc {device} \u4e0a\u7684 PIN \u78bc\u3002" + }, + "pairing": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {device}\uff1f\u5047\u5982\u60a8\u4e4b\u524d\u672a\u66fe\u9023\u7dda\u81f3 Home Assistant\uff0c\u61c9\u8a72\u6703\u65bc\u96fb\u8996\u4e0a\u6536\u5230\u9a57\u8b49\u8a0a\u606f\u3002" + }, "reauth_confirm": { - "description": "\u50b3\u9001\u5f8c\u3001\u8acb\u65bc 30 \u79d2\u5167\u540c\u610f {device} \u4e0a\u9396\u986f\u793a\u7684\u5f48\u51fa\u8996\u7a97\u6388\u6b0a\u8a31\u53ef\u3002" + "description": "\u50b3\u9001\u5f8c\u3001\u8acb\u65bc 30 \u79d2\u5167\u540c\u610f {device} \u4e0a\u9396\u986f\u793a\u7684\u5f48\u51fa\u8996\u7a97\u6388\u6b0a\u8a31\u53ef\u6216\u8f38\u5165 PIN\u3002" + }, + "reauth_confirm_encrypted": { + "description": "\u8acb\u8f38\u5165\u986f\u793a\u65bc {device} \u4e0a\u7684 PIN \u78bc\u3002" }, "user": { "data": { diff --git a/homeassistant/components/satel_integra/alarm_control_panel.py b/homeassistant/components/satel_integra/alarm_control_panel.py index b334a7110f6..4c036b4be85 100644 --- a/homeassistant/components/satel_integra/alarm_control_panel.py +++ b/homeassistant/components/satel_integra/alarm_control_panel.py @@ -8,10 +8,7 @@ import logging from satel_integra.satel_integra import AlarmState import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, @@ -64,6 +61,11 @@ async def async_setup_platform( class SatelIntegraAlarmPanel(alarm.AlarmControlPanelEntity): """Representation of an AlarmDecoder-based alarm panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + def __init__(self, controller, name, arm_home_mode, partition_id): """Initialize the alarm panel.""" self._name = name @@ -140,18 +142,13 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanelEntity): @property def code_format(self): """Return the regex for code format or None if no code is required.""" - return alarm.FORMAT_NUMBER + return alarm.CodeFormat.NUMBER @property def state(self): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY - async def async_alarm_disarm(self, code=None): """Send disarm command.""" if not code: diff --git a/homeassistant/components/schluter/climate.py b/homeassistant/components/schluter/climate.py index 08dc63e6365..5c848d7f7ea 100644 --- a/homeassistant/components/schluter/climate.py +++ b/homeassistant/components/schluter/climate.py @@ -13,10 +13,9 @@ from homeassistant.components.climate import ( ClimateEntity, ) from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant @@ -80,18 +79,16 @@ async def async_setup_platform( class SchluterThermostat(CoordinatorEntity, ClimateEntity): """Representation of a Schluter thermostat.""" + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__(self, coordinator, serial_number, api, session_id): """Initialize the thermostat.""" super().__init__(coordinator) self._serial_number = serial_number self._api = api self._session_id = session_id - self._support_flags = SUPPORT_TARGET_TEMPERATURE - - @property - def supported_features(self): - """Return the list of supported features.""" - return self._support_flags @property def unique_id(self): @@ -114,29 +111,17 @@ class SchluterThermostat(CoordinatorEntity, ClimateEntity): return self.coordinator.data[self._serial_number].temperature @property - def hvac_mode(self): - """Return current mode. Only heat available for floor thermostat.""" - return HVAC_MODE_HEAT - - @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return current operation. Can only be heating or idle.""" - return ( - CURRENT_HVAC_HEAT - if self.coordinator.data[self._serial_number].is_heating - else CURRENT_HVAC_IDLE - ) + if self.coordinator.data[self._serial_number].is_heating: + return HVACAction.HEATING + return HVACAction.IDLE @property def target_temperature(self): """Return the temperature we try to reach.""" return self.coordinator.data[self._serial_number].set_point_temp - @property - def hvac_modes(self): - """List of available operation modes.""" - return [HVAC_MODE_HEAT] - @property def min_temp(self): """Identify min_temp in Schluter API.""" @@ -147,7 +132,7 @@ class SchluterThermostat(CoordinatorEntity, ClimateEntity): """Identify max_temp in Schluter API.""" return self.coordinator.data[self._serial_number].max_temp - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Mode is always heating, so do nothing.""" def set_temperature(self, **kwargs): diff --git a/homeassistant/components/scrape/manifest.json b/homeassistant/components/scrape/manifest.json index 09e6b4a4c0b..bf5865206e4 100644 --- a/homeassistant/components/scrape/manifest.json +++ b/homeassistant/components/scrape/manifest.json @@ -2,7 +2,7 @@ "domain": "scrape", "name": "Scrape", "documentation": "https://www.home-assistant.io/integrations/scrape", - "requirements": ["beautifulsoup4==4.10.0"], + "requirements": ["beautifulsoup4==4.11.1"], "after_dependencies": ["rest"], "codeowners": ["@fabaff"], "iot_class": "cloud_polling" diff --git a/homeassistant/components/screenlogic/climate.py b/homeassistant/components/screenlogic/climate.py index 8fba805c74c..adc103fa684 100644 --- a/homeassistant/components/screenlogic/climate.py +++ b/homeassistant/components/screenlogic/climate.py @@ -6,13 +6,9 @@ from screenlogicpy.const import DATA as SL_DATA, EQUIPMENT, HEAT_MODE from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_PRESET_MODE, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -26,9 +22,8 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE -SUPPORTED_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT] +SUPPORTED_MODES = [HVACMode.OFF, HVACMode.HEAT] SUPPORTED_PRESETS = [ HEAT_MODE.SOLAR, @@ -55,6 +50,11 @@ async def async_setup_entry( class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): """Represents a ScreenLogic climate entity.""" + _attr_hvac_modes = SUPPORTED_MODES + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, coordinator, body): """Initialize a ScreenLogic climate entity.""" super().__init__(coordinator, body) @@ -101,30 +101,25 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): return TEMP_FAHRENHEIT @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return the current hvac mode.""" if self.body["heat_mode"]["value"] > 0: - return HVAC_MODE_HEAT - return HVAC_MODE_OFF + return HVACMode.HEAT + return HVACMode.OFF @property - def hvac_modes(self): - """Return th supported hvac modes.""" - return SUPPORTED_MODES - - @property - def hvac_action(self) -> str: + def hvac_action(self) -> HVACAction: """Return the current action of the heater.""" if self.body["heat_status"]["value"] > 0: - return CURRENT_HVAC_HEAT - if self.hvac_mode == HVAC_MODE_HEAT: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + return HVACAction.HEATING + if self.hvac_mode == HVACMode.HEAT: + return HVACAction.IDLE + return HVACAction.OFF @property def preset_mode(self) -> str: """Return current/last preset mode.""" - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: return HEAT_MODE.NAME_FOR_NUM[self._last_preset] return HEAT_MODE.NAME_FOR_NUM[self.body["heat_mode"]["value"]] @@ -135,11 +130,6 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): HEAT_MODE.NAME_FOR_NUM[mode_num] for mode_num in self._configured_heat_modes ] - @property - def supported_features(self): - """Supported features of the heater.""" - return SUPPORTED_FEATURES - async def async_set_temperature(self, **kwargs) -> None: """Change the setpoint of the heater.""" if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: @@ -156,7 +146,7 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): async def async_set_hvac_mode(self, hvac_mode) -> None: """Set the operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: mode = HEAT_MODE.OFF else: mode = HEAT_MODE.NUM_FOR_NAME[self.preset_mode] @@ -172,7 +162,7 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): """Set the preset mode.""" _LOGGER.debug("Setting last_preset to %s", HEAT_MODE.NUM_FOR_NAME[preset_mode]) self._last_preset = mode = HEAT_MODE.NUM_FOR_NAME[preset_mode] - if self.hvac_mode == HVAC_MODE_OFF: + if self.hvac_mode == HVACMode.OFF: return if await self.gateway.async_set_heat_mode(int(self._data_key), int(mode)): diff --git a/homeassistant/components/screenlogic/light.py b/homeassistant/components/screenlogic/light.py index c003a756974..c8067670942 100644 --- a/homeassistant/components/screenlogic/light.py +++ b/homeassistant/components/screenlogic/light.py @@ -3,7 +3,7 @@ import logging from screenlogicpy.const import DATA as SL_DATA, GENERIC_CIRCUIT_NAMES -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -34,3 +34,6 @@ async def async_setup_entry( class ScreenLogicLight(ScreenLogicCircuitEntity, LightEntity): """Class to represent a ScreenLogic Light.""" + + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index 7b3c9b0c1f8..beab664f448 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -18,6 +18,16 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ScreenlogicEntity from .const import DOMAIN +SUPPORTED_BASIC_SENSORS = ( + "air_temperature", + "saturation", +) + +SUPPORTED_BASIC_CHEM_SENSORS = ( + "orp", + "ph", +) + SUPPORTED_CHEM_SENSORS = ( "calcium_harness", "current_orp", @@ -27,11 +37,13 @@ SUPPORTED_CHEM_SENSORS = ( "orp_last_dose_time", "orp_last_dose_volume", "orp_setpoint", + "orp_supply_level", "ph_dosing_state", "ph_last_dose_time", "ph_last_dose_volume", "ph_probe_water_temp", "ph_setpoint", + "ph_supply_level", "salt_tds_ppm", "total_alkalinity", ) @@ -60,10 +72,17 @@ async def async_setup_entry( equipment_flags = coordinator.data[SL_DATA.KEY_CONFIG]["equipment_flags"] # Generic sensors - for sensor_name, sensor_data in coordinator.data[SL_DATA.KEY_SENSORS].items(): - if sensor_name in ("chem_alarm", "salt_ppm"): - continue - if sensor_data["value"] != 0: + for sensor_name in coordinator.data[SL_DATA.KEY_SENSORS]: + if sensor_name in SUPPORTED_BASIC_SENSORS: + entities.append(ScreenLogicSensor(coordinator, sensor_name)) + + # While these values exist in the chemistry data, their last value doesn't + # persist there when the pump is off/there is no flow. Pulling them from + # the basic sensors keeps the 'last' value and is better for graphs. + if ( + equipment_flags & EQUIPMENT.FLAG_INTELLICHEM + and sensor_name in SUPPORTED_BASIC_CHEM_SENSORS + ): entities.append(ScreenLogicSensor(coordinator, sensor_name)) # Pump sensors @@ -136,8 +155,7 @@ class ScreenLogicSensor(ScreenlogicEntity, SensorEntity): @property def native_value(self): """State of the sensor.""" - value = self.sensor["value"] - return (value - 1) if "supply" in self._data_key else value + return self.sensor["value"] @property def sensor(self): @@ -174,7 +192,7 @@ class ScreenLogicChemistrySensor(ScreenLogicSensor): value = self.sensor["value"] if "dosing_state" in self._key: return CHEM_DOSING_STATE.NAME_FOR_NUM[value] - return value + return (value - 1) if "supply" in self._data_key else value @property def sensor(self): diff --git a/homeassistant/components/screenlogic/translations/fr.json b/homeassistant/components/screenlogic/translations/fr.json index 62f26aae2b6..458deec97de 100644 --- a/homeassistant/components/screenlogic/translations/fr.json +++ b/homeassistant/components/screenlogic/translations/fr.json @@ -20,7 +20,7 @@ "data": { "selected_gateway": "Passerelle" }, - "description": "Les passerelles ScreenLogic suivantes ont \u00e9t\u00e9 d\u00e9couvertes. S\u2019il vous pla\u00eet s\u00e9lectionner un \u00e0 configurer, ou choisissez de configurer manuellement une passerelle ScreenLogic.", + "description": "Les passerelles ScreenLogic suivantes ont \u00e9t\u00e9 d\u00e9couvertes. Veuillez s\u00e9lectionner celle \u00e0 configurer. Vous pouvez \u00e9galement choisir de configurer une passerelle ScreenLogic manuellement.", "title": "ScreenLogic" } } diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 2b9c9976ce4..efad242fbd0 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -32,6 +32,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import ( ATTR_CUR, @@ -39,6 +42,7 @@ from homeassistant.helpers.script import ( CONF_MAX, CONF_MAX_EXCEEDED, Script, + script_stack_cv, ) from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.trace import trace_get, trace_path @@ -165,6 +169,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Load the scripts from the configuration.""" hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) + # To register scripts as valid domain for Blueprint async_get_blueprints(hass) @@ -312,6 +320,10 @@ class ScriptEntity(ToggleEntity, RestoreEntity): self.description = cfg[CONF_DESCRIPTION] self.fields = cfg[CONF_FIELDS] + # The object ID of scripts need / are unique already + # they cannot be changed from the UI after creating + self._attr_unique_id = object_id + self.entity_id = ENTITY_ID_FORMAT.format(object_id) self.script = Script( hass, @@ -387,10 +399,14 @@ class ScriptEntity(ToggleEntity, RestoreEntity): return # Caller does not want to wait for called script to finish so let script run in - # separate Task. However, wait for first state change so we can guarantee that - # it is written to the State Machine before we return. + # separate Task. Make a new empty script stack; scripts are allowed to + # recursively turn themselves on when not waiting. + script_stack_cv.set([]) + self._changed.clear() self.hass.async_create_task(coro) + # Wait for first state change so we can guarantee that + # it is written to the State Machine before we return. await self._changed.wait() async def _async_run(self, variables, context): diff --git a/homeassistant/components/script/config.py b/homeassistant/components/script/config.py index 24c5f86078b..bd7ca1fc791 100644 --- a/homeassistant/components/script/config.py +++ b/homeassistant/components/script/config.py @@ -35,6 +35,7 @@ from .const import ( CONF_REQUIRED, CONF_TRACE, DOMAIN, + LOGGER, ) from .helpers import async_get_blueprints @@ -110,6 +111,9 @@ async def async_validate_config(hass, config): scripts = {} for _, p_config in config_per_platform(config, DOMAIN): for object_id, cfg in p_config.items(): + if object_id in scripts: + LOGGER.warning("Duplicate script detected with name: '%s'", object_id) + continue cfg = await _try_async_validate_config_item(hass, object_id, cfg, config) if cfg is not None: scripts[object_id] = cfg diff --git a/homeassistant/components/script/recorder.py b/homeassistant/components/script/recorder.py new file mode 100644 index 00000000000..b1afc318b51 --- /dev/null +++ b/homeassistant/components/script/recorder.py @@ -0,0 +1,12 @@ +"""Integration platform for recorder.""" +from __future__ import annotations + +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_CUR, ATTR_LAST_ACTION, ATTR_LAST_TRIGGERED, ATTR_MAX, ATTR_MODE + + +@callback +def exclude_attributes(hass: HomeAssistant) -> set[str]: + """Exclude extra attributes from being recorded in the database.""" + return {ATTR_LAST_TRIGGERED, ATTR_MODE, ATTR_CUR, ATTR_MAX, ATTR_LAST_ACTION} diff --git a/homeassistant/components/scsgate/light.py b/homeassistant/components/scsgate/light.py index 161205cb373..3957aa4cd95 100644 --- a/homeassistant/components/scsgate/light.py +++ b/homeassistant/components/scsgate/light.py @@ -6,7 +6,7 @@ import logging from scsgate.tasks import ToggleStatusTask import voluptuous as vol -from homeassistant.components.light import PLATFORM_SCHEMA, LightEntity +from homeassistant.components.light import PLATFORM_SCHEMA, ColorMode, LightEntity from homeassistant.const import ATTR_ENTITY_ID, ATTR_STATE, CONF_DEVICES, CONF_NAME from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -54,9 +54,13 @@ def setup_platform( class SCSGateLight(LightEntity): """Representation of a SCSGate light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + _attr_should_poll = False + def __init__(self, scs_id, name, logger, scsgate): """Initialize the light.""" - self._name = name + self._attr_name = name self._scs_id = scs_id self._toggled = False self._logger = logger @@ -67,16 +71,6 @@ class SCSGateLight(LightEntity): """Return the SCS ID.""" return self._scs_id - @property - def should_poll(self): - """No polling needed for a SCSGate light.""" - return False - - @property - def name(self): - """Return the name of the device if any.""" - return self._name - @property def is_on(self): """Return true if light is on.""" diff --git a/homeassistant/components/season/translations/cs.json b/homeassistant/components/season/translations/cs.json new file mode 100644 index 00000000000..17ff202bb38 --- /dev/null +++ b/homeassistant/components/season/translations/cs.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Slu\u017eba je ji\u017e nastavena" + }, + "step": { + "user": { + "data": { + "type": "Typ definice sez\u00f3ny" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/cs.json b/homeassistant/components/sense/translations/cs.json index 59984b18992..aa5613d3ab4 100644 --- a/homeassistant/components/sense/translations/cs.json +++ b/homeassistant/components/sense/translations/cs.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", @@ -9,6 +10,12 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { + "reauth_validate": { + "data": { + "password": "Heslo" + }, + "title": "Znovu ov\u011b\u0159it integraci" + }, "user": { "data": { "email": "E-mail", diff --git a/homeassistant/components/sense/translations/fr.json b/homeassistant/components/sense/translations/fr.json index 000240517b9..ece97b4df4f 100644 --- a/homeassistant/components/sense/translations/fr.json +++ b/homeassistant/components/sense/translations/fr.json @@ -14,6 +14,7 @@ "data": { "password": "Mot de passe" }, + "description": "L'int\u00e9gration Sense doit r\u00e9-authentifier votre compte {email}.", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { @@ -27,7 +28,8 @@ "validation": { "data": { "code": "Code de v\u00e9rification" - } + }, + "title": "Authentification multi-facteurs Sense" } } } diff --git a/homeassistant/components/senseme/fan.py b/homeassistant/components/senseme/fan.py index 8dd31343058..63c64a11632 100644 --- a/homeassistant/components/senseme/fan.py +++ b/homeassistant/components/senseme/fan.py @@ -10,9 +10,8 @@ from homeassistant import config_entries from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, - SUPPORT_DIRECTION, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -50,7 +49,7 @@ async def async_setup_entry( class HASensemeFan(SensemeEntity, FanEntity): """SenseME ceiling fan component.""" - _attr_supported_features = SUPPORT_SET_SPEED | SUPPORT_DIRECTION + _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION _attr_preset_modes = [PRESET_MODE_WHOOSH] def __init__(self, device: SensemeFan) -> None: diff --git a/homeassistant/components/senseme/light.py b/homeassistant/components/senseme/light.py index 3036dc1d04d..f1150260f38 100644 --- a/homeassistant/components/senseme/light.py +++ b/homeassistant/components/senseme/light.py @@ -9,8 +9,7 @@ from homeassistant import config_entries from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, + ColorMode, LightEntity, ) from homeassistant.core import HomeAssistant, callback @@ -75,8 +74,8 @@ class HASensemeFanLight(HASensemeLight): def __init__(self, device: SensemeDevice) -> None: """Init a fan light.""" super().__init__(device, device.name) - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS class HASensemeStandaloneLight(HASensemeLight): @@ -85,8 +84,8 @@ class HASensemeStandaloneLight(HASensemeLight): def __init__(self, device: SensemeDevice) -> None: """Init a standalone light.""" super().__init__(device, f"{device.name} Light") - self._attr_supported_color_modes = {COLOR_MODE_COLOR_TEMP} - self._attr_color_mode = COLOR_MODE_COLOR_TEMP + self._attr_supported_color_modes = {ColorMode.COLOR_TEMP} + self._attr_color_mode = ColorMode.COLOR_TEMP self._attr_min_mireds = color_temperature_kelvin_to_mired( device.light_color_temp_max ) diff --git a/homeassistant/components/sensibo/binary_sensor.py b/homeassistant/components/sensibo/binary_sensor.py index 02f86dbe009..27e551a51c8 100644 --- a/homeassistant/components/sensibo/binary_sensor.py +++ b/homeassistant/components/sensibo/binary_sensor.py @@ -82,14 +82,6 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = ( icon="mdi:motion-sensor", value_fn=lambda data: data.room_occupied, ), - SensiboDeviceBinarySensorEntityDescription( - key="update_available", - device_class=BinarySensorDeviceClass.UPDATE, - entity_category=EntityCategory.DIAGNOSTIC, - name="Update Available", - icon="mdi:rocket-launch", - value_fn=lambda data: data.update_available, - ), ) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 8f7b671a948..907105b5384 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -4,17 +4,7 @@ from __future__ import annotations import voluptuous as vol from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_STATE, @@ -36,18 +26,18 @@ from .entity import SensiboDeviceBaseEntity SERVICE_ASSUME_STATE = "assume_state" FIELD_TO_FLAG = { - "fanLevel": SUPPORT_FAN_MODE, - "swing": SUPPORT_SWING_MODE, - "targetTemperature": SUPPORT_TARGET_TEMPERATURE, + "fanLevel": ClimateEntityFeature.FAN_MODE, + "swing": ClimateEntityFeature.SWING_MODE, + "targetTemperature": ClimateEntityFeature.TARGET_TEMPERATURE, } SENSIBO_TO_HA = { - "cool": HVAC_MODE_COOL, - "heat": HVAC_MODE_HEAT, - "fan": HVAC_MODE_FAN_ONLY, - "auto": HVAC_MODE_HEAT_COOL, - "dry": HVAC_MODE_DRY, - "off": HVAC_MODE_OFF, + "cool": HVACMode.COOL, + "heat": HVACMode.HEAT, + "fan": HVACMode.FAN_ONLY, + "auto": HVACMode.HEAT_COOL, + "dry": HVACMode.DRY, + "off": HVACMode.OFF, } HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()} @@ -55,7 +45,7 @@ HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()} AC_STATE_TO_DATA = { "targetTemperature": "target_temp", "fanLevel": "fan_mode", - "on": "on", + "on": "device_on", "mode": "hvac_mode", "swing": "swing_mode", } @@ -115,16 +105,14 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): return self.device_data.humidity @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation.""" - return ( - SENSIBO_TO_HA[self.device_data.hvac_mode] - if self.device_data.device_on - else HVAC_MODE_OFF - ) + if self.device_data.device_on: + return SENSIBO_TO_HA[self.device_data.hvac_mode] + return HVACMode.OFF @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" return [SENSIBO_TO_HA[mode] for mode in self.device_data.hvac_modes] @@ -215,9 +203,9 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): await self._async_set_ac_state_property("fanLevel", fan_mode) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._async_set_ac_state_property("on", False) return @@ -267,5 +255,5 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): async def async_assume_state(self, state) -> None: """Sync state with api.""" - await self._async_set_ac_state_property("on", state != HVAC_MODE_OFF, True) + await self._async_set_ac_state_property("on", state != HVACMode.OFF, True) await self.coordinator.async_refresh() diff --git a/homeassistant/components/sensibo/const.py b/homeassistant/components/sensibo/const.py index ac3df435e29..5fce3822bb2 100644 --- a/homeassistant/components/sensibo/const.py +++ b/homeassistant/components/sensibo/const.py @@ -18,6 +18,7 @@ PLATFORMS = [ Platform.NUMBER, Platform.SELECT, Platform.SENSOR, + Platform.UPDATE, ] DEFAULT_NAME = "Sensibo" TIMEOUT = 8 diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index 233bf009c75..308d991e675 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -2,7 +2,7 @@ "domain": "sensibo", "name": "Sensibo", "documentation": "https://www.home-assistant.io/integrations/sensibo", - "requirements": ["pysensibo==1.0.9"], + "requirements": ["pysensibo==1.0.14"], "config_flow": true, "codeowners": ["@andrey-git", "@gjohansson-ST"], "iot_class": "cloud_polling", diff --git a/homeassistant/components/sensibo/translations/hu.json b/homeassistant/components/sensibo/translations/hu.json index 802b718cc82..065a7b982ed 100644 --- a/homeassistant/components/sensibo/translations/hu.json +++ b/homeassistant/components/sensibo/translations/hu.json @@ -20,7 +20,7 @@ "user": { "data": { "api_key": "API kulcs", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" } } } diff --git a/homeassistant/components/sensibo/update.py b/homeassistant/components/sensibo/update.py new file mode 100644 index 00000000000..6e227a891b0 --- /dev/null +++ b/homeassistant/components/sensibo/update.py @@ -0,0 +1,93 @@ +"""Update platform for Sensibo integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from pysensibo.model import SensiboDevice + +from homeassistant.components.update import ( + UpdateDeviceClass, + UpdateEntity, + UpdateEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import SensiboDataUpdateCoordinator +from .entity import SensiboDeviceBaseEntity + + +@dataclass +class DeviceBaseEntityDescriptionMixin: + """Mixin for required Sensibo base description keys.""" + + value_version: Callable[[SensiboDevice], str | None] + value_available: Callable[[SensiboDevice], str | None] + + +@dataclass +class SensiboDeviceUpdateEntityDescription( + UpdateEntityDescription, DeviceBaseEntityDescriptionMixin +): + """Describes Sensibo Update entity.""" + + +DEVICE_SENSOR_TYPES: tuple[SensiboDeviceUpdateEntityDescription, ...] = ( + SensiboDeviceUpdateEntityDescription( + key="fw_ver_available", + device_class=UpdateDeviceClass.FIRMWARE, + entity_category=EntityCategory.DIAGNOSTIC, + name="Update Available", + icon="mdi:rocket-launch", + value_version=lambda data: data.fw_ver, + value_available=lambda data: data.fw_ver_available, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Sensibo Update platform.""" + + coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + SensiboDeviceUpdate(coordinator, device_id, description) + for description in DEVICE_SENSOR_TYPES + for device_id, device_data in coordinator.data.parsed.items() + if description.value_available(device_data) is not None + ) + + +class SensiboDeviceUpdate(SensiboDeviceBaseEntity, UpdateEntity): + """Representation of a Sensibo Device Update.""" + + entity_description: SensiboDeviceUpdateEntityDescription + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + entity_description: SensiboDeviceUpdateEntityDescription, + ) -> None: + """Initiate Sensibo Device Update.""" + super().__init__(coordinator, device_id) + self.entity_description = entity_description + self._attr_unique_id = f"{device_id}-{entity_description.key}" + self._attr_name = f"{self.device_data.name} {entity_description.name}" + self._attr_title = self.device_data.model + + @property + def installed_version(self) -> str | None: + """Return version currently installed.""" + return self.entity_description.value_version(self.device_data) + + @property + def latest_version(self) -> str | None: + """Return latest available version.""" + return self.entity_description.value_available(self.device_data) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index b9cb3d94796..6a69c27f9b6 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Mapping from contextlib import suppress from dataclasses import dataclass from datetime import date, datetime, timedelta, timezone -import inspect +from decimal import Decimal, InvalidOperation as DecimalInvalidOperation import logging from math import floor, log10 from typing import Any, Final, cast, final @@ -14,7 +14,7 @@ import voluptuous as vol from homeassistant.backports.enum import StrEnum from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( # noqa: F401 +from homeassistant.const import ( # noqa: F401, pylint: disable=[hass-deprecated-import] CONF_UNIT_OF_MEASUREMENT, DEVICE_CLASS_AQI, DEVICE_CLASS_BATTERY, @@ -101,6 +101,9 @@ class SensorDeviceClass(StrEnum): # date (ISO8601) DATE = "date" + # fixed duration (TIME_DAYS, TIME_HOURS, TIME_MINUTES, TIME_SECONDS) + DURATION = "duration" + # energy (Wh, kWh, MWh) ENERGY = "energy" @@ -253,27 +256,6 @@ class SensorEntityDescription(EntityDescription): state_class: SensorStateClass | str | None = None unit_of_measurement: None = None # Type override, use native_unit_of_measurement - def __post_init__(self) -> None: - """Post initialisation processing.""" - if self.unit_of_measurement: - caller = inspect.stack()[2] # type: ignore[unreachable] - module = inspect.getmodule(caller[0]) - if "custom_components" in module.__file__: - report_issue = "report it to the custom component author." - else: - report_issue = ( - "create a bug report at " - "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue" - ) - _LOGGER.warning( - "%s is setting 'unit_of_measurement' on an instance of " - "SensorEntityDescription, this is not valid and will be unsupported " - "from Home Assistant 2021.11. Please %s", - module.__name__, - report_issue, - ) - self.native_unit_of_measurement = self.unit_of_measurement - class SensorEntity(Entity): """Base class for sensor entities.""" @@ -387,16 +369,12 @@ class SensorEntity(Entity): if self._sensor_option_unit_of_measurement: return self._sensor_option_unit_of_measurement - # Support for _attr_unit_of_measurement will be removed in Home Assistant 2021.11 - if ( - hasattr(self, "_attr_unit_of_measurement") - and self._attr_unit_of_measurement is not None - ): - return self._attr_unit_of_measurement # type: ignore[unreachable] - native_unit_of_measurement = self.native_unit_of_measurement - if native_unit_of_measurement in (TEMP_CELSIUS, TEMP_FAHRENHEIT): + if ( + self.device_class == DEVICE_CLASS_TEMPERATURE + and native_unit_of_measurement in (TEMP_CELSIUS, TEMP_FAHRENHEIT) + ): return self.hass.config.units.temperature_unit return native_unit_of_measurement @@ -457,7 +435,7 @@ class SensorEntity(Entity): prec = len(value_s) - value_s.index(".") - 1 if "." in value_s else 0 # Scale the precision when converting to a larger unit - # For example 1.1 kWh should be rendered as 0.0011 kWh, not 0.0 kWh + # For example 1.1 Wh should be rendered as 0.0011 kWh, not 0.0 kWh ratio_log = max( 0, log10( @@ -479,37 +457,6 @@ class SensorEntity(Entity): # Round to the wanted precision value = round(value_f_new) if prec == 0 else round(value_f_new, prec) - elif ( - value is not None - and self.device_class != DEVICE_CLASS_TEMPERATURE - and native_unit_of_measurement != self.hass.config.units.temperature_unit - and native_unit_of_measurement in (TEMP_CELSIUS, TEMP_FAHRENHEIT) - ): - units = self.hass.config.units - if not self._temperature_conversion_reported: - self._temperature_conversion_reported = True - report_issue = self._suggest_report_issue() - _LOGGER.warning( - "Entity %s (%s) with device_class %s reports a temperature in " - "%s which will be converted to %s. Temperature conversion for " - "entities without correct device_class is deprecated and will" - " be removed from Home Assistant Core 2022.3. Please update " - "your configuration if device_class is manually configured, " - "otherwise %s", - self.entity_id, - type(self), - self.device_class, - native_unit_of_measurement, - units.temperature_unit, - report_issue, - ) - value_s = str(value) - prec = len(value_s) - value_s.index(".") - 1 if "." in value_s else 0 - # Suppress ValueError (Could not convert sensor_value to float) - with suppress(ValueError): - temp = units.temperature(float(value), native_unit_of_measurement) # type: ignore[arg-type] - value = round(temp) if prec == 0 else round(temp, prec) - return value def __repr__(self) -> str: @@ -544,17 +491,24 @@ class SensorEntity(Entity): class SensorExtraStoredData(ExtraStoredData): """Object to hold extra stored data.""" - native_value: StateType | date | datetime + native_value: StateType | date | datetime | Decimal native_unit_of_measurement: str | None def as_dict(self) -> dict[str, Any]: """Return a dict representation of the sensor data.""" - native_value: StateType | date | datetime | dict[str, str] = self.native_value + native_value: StateType | date | datetime | Decimal | dict[ + str, str + ] = self.native_value if isinstance(native_value, (date, datetime)): native_value = { "__type": str(type(native_value)), "isoformat": native_value.isoformat(), } + if isinstance(native_value, Decimal): + native_value = { + "__type": str(type(native_value)), + "decimal_str": str(native_value), + } return { "native_value": native_value, "native_unit_of_measurement": self.native_unit_of_measurement, @@ -574,12 +528,17 @@ class SensorExtraStoredData(ExtraStoredData): native_value = dt_util.parse_datetime(native_value["isoformat"]) elif type_ == "": native_value = dt_util.parse_date(native_value["isoformat"]) + elif type_ == "": + native_value = Decimal(native_value["decimal_str"]) except TypeError: # native_value is not a dict pass except KeyError: # native_value is a dict, but does not have all values return None + except DecimalInvalidOperation: + # native_value coulnd't be returned from decimal_str + return None return cls(native_value, native_unit_of_measurement) diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 47ac6c048ce..70a265e3b25 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -16,14 +16,18 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import condition, config_validation as cv -from homeassistant.helpers.entity import get_device_class, get_unit_of_measurement +from homeassistant.helpers.entity import ( + get_capability, + get_device_class, + get_unit_of_measurement, +) from homeassistant.helpers.entity_registry import ( async_entries_for_device, async_get_registry, ) from homeassistant.helpers.typing import ConfigType -from . import DOMAIN, SensorDeviceClass +from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass # mypy: allow-untyped-defs, no-check-untyped-defs @@ -146,9 +150,10 @@ async def async_get_conditions( for entry in entries: device_class = get_device_class(hass, entry.entity_id) or DEVICE_CLASS_NONE + state_class = get_capability(hass, entry.entity_id, ATTR_STATE_CLASS) unit_of_measurement = get_unit_of_measurement(hass, entry.entity_id) - if not unit_of_measurement: + if not unit_of_measurement and not state_class: continue templates = ENTITY_CONDITIONS.get( diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 80decdfcb7a..f90022cf5f3 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -17,10 +17,14 @@ from homeassistant.const import ( ) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import get_device_class, get_unit_of_measurement +from homeassistant.helpers.entity import ( + get_capability, + get_device_class, + get_unit_of_measurement, +) from homeassistant.helpers.entity_registry import async_entries_for_device -from . import DOMAIN, SensorDeviceClass +from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass # mypy: allow-untyped-defs, no-check-untyped-defs @@ -165,9 +169,10 @@ async def async_get_triggers(hass, device_id): for entry in entries: device_class = get_device_class(hass, entry.entity_id) or DEVICE_CLASS_NONE + state_class = get_capability(hass, entry.entity_id, ATTR_STATE_CLASS) unit_of_measurement = get_unit_of_measurement(hass, entry.entity_id) - if not unit_of_measurement: + if not unit_of_measurement and not state_class: continue templates = ENTITY_TRIGGERS.get( diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index ae148d45e72..3fc5cbec7ee 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -7,7 +7,7 @@ import datetime import itertools import logging import math -from typing import Any, cast +from typing import Any from sqlalchemy.orm.session import Session @@ -19,7 +19,6 @@ from homeassistant.components.recorder import ( ) from homeassistant.components.recorder.const import DOMAIN as RECORDER_DOMAIN from homeassistant.components.recorder.models import ( - LazyState, StatisticData, StatisticMetaData, StatisticResult, @@ -388,14 +387,14 @@ def _last_reset_as_utc_isoformat(last_reset_s: Any, entity_id: str) -> str | Non def compile_statistics( hass: HomeAssistant, start: datetime.datetime, end: datetime.datetime -) -> list[StatisticResult]: +) -> statistics.PlatformCompiledStatistics: """Compile statistics for all entities during start-end. Note: This will query the database and must not be run in the event loop """ with recorder_util.session_scope(hass=hass) as session: - result = _compile_statistics(hass, session, start, end) - return result + compiled = _compile_statistics(hass, session, start, end) + return compiled def _compile_statistics( # noqa: C901 @@ -403,7 +402,7 @@ def _compile_statistics( # noqa: C901 session: Session, start: datetime.datetime, end: datetime.datetime, -) -> list[StatisticResult]: +) -> statistics.PlatformCompiledStatistics: """Compile statistics for all entities during start-end.""" result: list[StatisticResult] = [] @@ -417,9 +416,9 @@ def _compile_statistics( # noqa: C901 entities_full_history = [ i.entity_id for i in sensor_states if "sum" in wanted_statistics[i.entity_id] ] - history_list: MutableMapping[str, Iterable[LazyState | State | dict[str, Any]]] = {} + history_list: MutableMapping[str, list[State]] = {} if entities_full_history: - history_list = history.get_significant_states_with_session( + history_list = history.get_full_significant_states_with_session( hass, session, start - datetime.timedelta.resolution, @@ -433,7 +432,7 @@ def _compile_statistics( # noqa: C901 if "sum" not in wanted_statistics[i.entity_id] ] if entities_significant_history: - _history_list = history.get_significant_states_with_session( + _history_list = history.get_full_significant_states_with_session( hass, session, start - datetime.timedelta.resolution, @@ -445,23 +444,22 @@ def _compile_statistics( # noqa: C901 # from the recorder. Get the state from the state machine instead. for _state in sensor_states: if _state.entity_id not in history_list: - history_list[_state.entity_id] = (_state,) + history_list[_state.entity_id] = [_state] - for _state in sensor_states: # pylint: disable=too-many-nested-blocks + to_process = [] + to_query = [] + for _state in sensor_states: entity_id = _state.entity_id if entity_id not in history_list: continue - state_class = _state.attributes[ATTR_STATE_CLASS] device_class = _state.attributes.get(ATTR_DEVICE_CLASS) entity_history = history_list[entity_id] unit, fstates = _normalize_states( hass, session, old_metadatas, - # entity_history does not contain minimal responses - # so we must cast here - cast(list[State], entity_history), + entity_history, device_class, entity_id, ) @@ -469,6 +467,21 @@ def _compile_statistics( # noqa: C901 if not fstates: continue + state_class = _state.attributes[ATTR_STATE_CLASS] + + to_process.append((entity_id, unit, state_class, fstates)) + if "sum" in wanted_statistics[entity_id]: + to_query.append(entity_id) + + last_stats = statistics.get_latest_short_term_statistics( + hass, to_query, metadata=old_metadatas + ) + for ( # pylint: disable=too-many-nested-blocks + entity_id, + unit, + state_class, + fstates, + ) in to_process: # Check metadata if old_metadata := old_metadatas.get(entity_id): if old_metadata[1]["unit_of_measurement"] != unit: @@ -514,9 +527,6 @@ def _compile_statistics( # noqa: C901 last_reset = old_last_reset = None new_state = old_state = None _sum = 0.0 - last_stats = statistics.get_last_short_term_statistics( - hass, 1, entity_id, False - ) if entity_id in last_stats: # We have compiled history for this sensor before, use that as a starting point last_reset = old_last_reset = last_stats[entity_id][0]["last_reset"] @@ -601,7 +611,7 @@ def _compile_statistics( # noqa: C901 result.append({"meta": meta, "stat": stat}) - return result + return statistics.PlatformCompiledStatistics(result, old_metadatas) def list_statistic_ids( diff --git a/homeassistant/components/sensor/translations/pl.json b/homeassistant/components/sensor/translations/pl.json index 05c0a69708c..b6e93865425 100644 --- a/homeassistant/components/sensor/translations/pl.json +++ b/homeassistant/components/sensor/translations/pl.json @@ -7,8 +7,8 @@ "is_carbon_monoxide": "obecny poziom st\u0119\u017cenia tlenku w\u0119gla w {entity_name}", "is_current": "obecne nat\u0119\u017cenie pr\u0105du {entity_name}", "is_energy": "obecna energia {entity_name}", - "is_frequency": "Obecna cz\u0119stotliwo\u015b\u0107 {entity_name}", - "is_gas": "Obecny poziom gazu {entity_name}", + "is_frequency": "obecna cz\u0119stotliwo\u015b\u0107 {entity_name}", + "is_gas": "obecny poziom gazu {entity_name}", "is_humidity": "obecna wilgotno\u015b\u0107 {entity_name}", "is_illuminance": "obecne nat\u0119\u017cenie o\u015bwietlenia {entity_name}", "is_nitrogen_dioxide": "obecny poziom st\u0119\u017cenia dwutlenku azotu {entity_name}", diff --git a/homeassistant/components/sentry/manifest.json b/homeassistant/components/sentry/manifest.json index a8af46270ae..1096edd4e36 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,7 +3,7 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==1.5.8"], + "requirements": ["sentry-sdk==1.5.10"], "codeowners": ["@dcramer", "@frenck"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/sentry/translations/ca.json b/homeassistant/components/sentry/translations/ca.json index 25d5a5bf37e..d83abb16f1e 100644 --- a/homeassistant/components/sentry/translations/ca.json +++ b/homeassistant/components/sentry/translations/ca.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Introdueix el DSN de Sentry", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/de.json b/homeassistant/components/sentry/translations/de.json index 62aca6a65e9..d408af0d885 100644 --- a/homeassistant/components/sentry/translations/de.json +++ b/homeassistant/components/sentry/translations/de.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry-DSN" }, "description": "Gib deine Sentry-DSN ein", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/en.json b/homeassistant/components/sentry/translations/en.json index f00116c85d9..16bdac6b510 100644 --- a/homeassistant/components/sentry/translations/en.json +++ b/homeassistant/components/sentry/translations/en.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Enter your Sentry DSN", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/et.json b/homeassistant/components/sentry/translations/et.json index 5c501b2b0aa..01386fa97e6 100644 --- a/homeassistant/components/sentry/translations/et.json +++ b/homeassistant/components/sentry/translations/et.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "" + "dsn": "Sentry DSN" }, "description": "Sisesta oma Sentry DSN", "title": "" diff --git a/homeassistant/components/sentry/translations/fr.json b/homeassistant/components/sentry/translations/fr.json index acad5566ec3..6850bfe8a71 100644 --- a/homeassistant/components/sentry/translations/fr.json +++ b/homeassistant/components/sentry/translations/fr.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "DSN Sentry" }, "description": "Entrez votre DSN Sentry", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/he.json b/homeassistant/components/sentry/translations/he.json index 3383895686e..2a89b9e5556 100644 --- a/homeassistant/components/sentry/translations/he.json +++ b/homeassistant/components/sentry/translations/he.json @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" } } } diff --git a/homeassistant/components/sentry/translations/hu.json b/homeassistant/components/sentry/translations/hu.json index 9c28d57eb5d..0535657c3d0 100644 --- a/homeassistant/components/sentry/translations/hu.json +++ b/homeassistant/components/sentry/translations/hu.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Adja meg a Sentry DSN-t", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/id.json b/homeassistant/components/sentry/translations/id.json index b2004e8e3d5..5f81eb1a445 100644 --- a/homeassistant/components/sentry/translations/id.json +++ b/homeassistant/components/sentry/translations/id.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Masukkan DSN Sentry Anda", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/it.json b/homeassistant/components/sentry/translations/it.json index 9e3e6974883..10a0d1bad2b 100644 --- a/homeassistant/components/sentry/translations/it.json +++ b/homeassistant/components/sentry/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "DSN Sentry" }, "description": "Inserisci il tuo DSN Sentry", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 50af985b47c..67be245ffde 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Voer uw Sentry DSN in", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/no.json b/homeassistant/components/sentry/translations/no.json index 31f4ef30d12..c3bb4acd26e 100644 --- a/homeassistant/components/sentry/translations/no.json +++ b/homeassistant/components/sentry/translations/no.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Fyll inn din Sentry DNS", "title": "" diff --git a/homeassistant/components/sentry/translations/pl.json b/homeassistant/components/sentry/translations/pl.json index 8d1bc2092aa..be5d008dcff 100644 --- a/homeassistant/components/sentry/translations/pl.json +++ b/homeassistant/components/sentry/translations/pl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Wprowad\u017a DSN Sentry", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/pt-BR.json b/homeassistant/components/sentry/translations/pt-BR.json index 21f1e3ef91a..c489de8ee6d 100644 --- a/homeassistant/components/sentry/translations/pt-BR.json +++ b/homeassistant/components/sentry/translations/pt-BR.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentinela DSN" }, "description": "Digite seu DSN Sentry", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/ru.json b/homeassistant/components/sentry/translations/ru.json index eb6cbd0310c..6a7192a5385 100644 --- a/homeassistant/components/sentry/translations/ru.json +++ b/homeassistant/components/sentry/translations/ru.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448 DSN Sentry", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/tr.json b/homeassistant/components/sentry/translations/tr.json index fa3f39e9cde..6369f727d17 100644 --- a/homeassistant/components/sentry/translations/tr.json +++ b/homeassistant/components/sentry/translations/tr.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "Sentry DSN'nizi girin", "title": "Sentry" diff --git a/homeassistant/components/sentry/translations/zh-Hant.json b/homeassistant/components/sentry/translations/zh-Hant.json index 04fe4682a42..6d4615db892 100644 --- a/homeassistant/components/sentry/translations/zh-Hant.json +++ b/homeassistant/components/sentry/translations/zh-Hant.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "dsn": "DSN" + "dsn": "Sentry DSN" }, "description": "\u8f38\u5165 Sentry DSN", "title": "Sentry" diff --git a/homeassistant/components/senz/__init__.py b/homeassistant/components/senz/__init__.py new file mode 100644 index 00000000000..b6fc2422888 --- /dev/null +++ b/homeassistant/components/senz/__init__.py @@ -0,0 +1,116 @@ +"""The nVent RAYCHEM SENZ integration.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +from aiosenz import AUTHORIZATION_ENDPOINT, SENZAPI, TOKEN_ENDPOINT, Thermostat +from httpx import RequestError +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import ( + config_entry_oauth2_flow, + config_validation as cv, + httpx_client, +) +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from . import config_flow +from .api import SENZConfigEntryAuth +from .const import DOMAIN + +UPDATE_INTERVAL = timedelta(seconds=30) + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +PLATFORMS = [Platform.CLIMATE] + +SENZDataUpdateCoordinator = DataUpdateCoordinator[dict[str, Thermostat]] + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the SENZ OAuth2 configuration.""" + hass.data[DOMAIN] = {} + + if DOMAIN not in config: + return True + + config_flow.OAuth2FlowHandler.async_register_implementation( + hass, + config_entry_oauth2_flow.LocalOAuth2Implementation( + hass, + DOMAIN, + config[DOMAIN][CONF_CLIENT_ID], + config[DOMAIN][CONF_CLIENT_SECRET], + AUTHORIZATION_ENDPOINT, + TOKEN_ENDPOINT, + ), + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up SENZ from a config entry.""" + implementation = ( + await config_entry_oauth2_flow.async_get_config_entry_implementation( + hass, entry + ) + ) + session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) + auth = SENZConfigEntryAuth(httpx_client.get_async_client(hass), session) + senz_api = SENZAPI(auth) + + async def update_thermostats() -> dict[str, Thermostat]: + """Fetch SENZ thermostats data.""" + try: + thermostats = await senz_api.get_thermostats() + except RequestError as err: + raise UpdateFailed from err + return {thermostat.serial_number: thermostat for thermostat in thermostats} + + try: + account = await senz_api.get_account() + except RequestError as err: + raise ConfigEntryNotReady from err + + coordinator = SENZDataUpdateCoordinator( + hass, + _LOGGER, + name=account.username, + update_interval=UPDATE_INTERVAL, + update_method=update_thermostats, + ) + + await coordinator.async_config_entry_first_refresh() + + hass.data[DOMAIN][entry.entry_id] = coordinator + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/senz/api.py b/homeassistant/components/senz/api.py new file mode 100644 index 00000000000..1f0ccee3c7c --- /dev/null +++ b/homeassistant/components/senz/api.py @@ -0,0 +1,25 @@ +"""API for nVent RAYCHEM SENZ bound to Home Assistant OAuth.""" +from typing import cast + +from aiosenz import AbstractSENZAuth +from httpx import AsyncClient + +from homeassistant.helpers import config_entry_oauth2_flow + + +class SENZConfigEntryAuth(AbstractSENZAuth): + """Provide nVent RAYCHEM SENZ authentication tied to an OAuth2 based config entry.""" + + def __init__( + self, + httpx_async_client: AsyncClient, + oauth_session: config_entry_oauth2_flow.OAuth2Session, + ) -> None: + """Initialize SENZ auth.""" + super().__init__(httpx_async_client) + self._oauth_session = oauth_session + + async def get_access_token(self) -> str: + """Return a valid access token.""" + await self._oauth_session.async_ensure_token_valid() + return cast(str, self._oauth_session.token["access_token"]) diff --git a/homeassistant/components/senz/climate.py b/homeassistant/components/senz/climate.py new file mode 100644 index 00000000000..c03ca2732dd --- /dev/null +++ b/homeassistant/components/senz/climate.py @@ -0,0 +1,100 @@ +"""nVent RAYCHEM SENZ climate platform.""" +from __future__ import annotations + +from typing import Any + +from aiosenz import MODE_AUTO, Thermostat + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import SENZDataUpdateCoordinator +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the SENZ climate entities from a config entry.""" + coordinator: SENZDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + SENZClimate(thermostat, coordinator) for thermostat in coordinator.data.values() + ) + + +class SENZClimate(CoordinatorEntity, ClimateEntity): + """Representation of a SENZ climate entity.""" + + _attr_temperature_unit = TEMP_CELSIUS + _attr_precision = PRECISION_TENTHS + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.AUTO] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + _attr_max_temp = 35 + _attr_min_temp = 5 + + def __init__( + self, + thermostat: Thermostat, + coordinator: SENZDataUpdateCoordinator, + ) -> None: + """Init SENZ climate.""" + super().__init__(coordinator) + self._thermostat = thermostat + self._attr_name = thermostat.name + self._attr_unique_id = thermostat.serial_number + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, thermostat.serial_number)}, + manufacturer="nVent Raychem", + model="SENZ WIFI", + name=thermostat.name, + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._thermostat = self.coordinator.data[self._thermostat.serial_number] + self.async_write_ha_state() + + @property + def current_temperature(self) -> float: + """Return the current temperature.""" + return self._thermostat.current_temperatue + + @property + def target_temperature(self) -> float: + """Return the temperature we try to reach.""" + return self._thermostat.setpoint_temperature + + @property + def available(self) -> bool: + """Return True if the thermostat is available.""" + return self._thermostat.online + + @property + def hvac_mode(self) -> HVACMode: + """Return hvac operation ie. auto, heat mode.""" + if self._thermostat.mode == MODE_AUTO: + return HVACMode.AUTO + return HVACMode.HEAT + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + if hvac_mode == HVACMode.AUTO: + await self._thermostat.auto() + else: + await self._thermostat.manual() + await self.coordinator.async_request_refresh() + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new target temperature.""" + temp: float = kwargs[ATTR_TEMPERATURE] + await self._thermostat.manual(temp) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/senz/config_flow.py b/homeassistant/components/senz/config_flow.py new file mode 100644 index 00000000000..1bc38321370 --- /dev/null +++ b/homeassistant/components/senz/config_flow.py @@ -0,0 +1,24 @@ +"""Config flow for nVent RAYCHEM SENZ.""" +import logging + +from homeassistant.helpers import config_entry_oauth2_flow + +from .const import DOMAIN + + +class OAuth2FlowHandler( + config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN +): + """Config flow to handle SENZ OAuth2 authentication.""" + + DOMAIN = DOMAIN + + @property + def logger(self) -> logging.Logger: + """Return logger.""" + return logging.getLogger(__name__) + + @property + def extra_authorize_data(self) -> dict: + """Extra data that needs to be appended to the authorize url.""" + return {"scope": "restapi offline_access"} diff --git a/homeassistant/components/senz/const.py b/homeassistant/components/senz/const.py new file mode 100644 index 00000000000..664f2d91c6b --- /dev/null +++ b/homeassistant/components/senz/const.py @@ -0,0 +1,3 @@ +"""Constants for the nVent RAYCHEM SENZ integration.""" + +DOMAIN = "senz" diff --git a/homeassistant/components/senz/manifest.json b/homeassistant/components/senz/manifest.json new file mode 100644 index 00000000000..e9b6165cb26 --- /dev/null +++ b/homeassistant/components/senz/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "senz", + "name": "nVent RAYCHEM SENZ", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/senz", + "requirements": ["aiosenz==1.0.0"], + "dependencies": ["auth"], + "codeowners": ["@milanmeu"], + "iot_class": "cloud_polling" +} diff --git a/homeassistant/components/senz/strings.json b/homeassistant/components/senz/strings.json new file mode 100644 index 00000000000..316f7234f9b --- /dev/null +++ b/homeassistant/components/senz/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]", + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", + "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" + }, + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } + } +} diff --git a/homeassistant/components/senz/translations/bg.json b/homeassistant/components/senz/translations/bg.json new file mode 100644 index 00000000000..ddd6040e9cf --- /dev/null +++ b/homeassistant/components/senz/translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/ca.json b/homeassistant/components/senz/translations/ca.json new file mode 100644 index 00000000000..20b2ceceddd --- /dev/null +++ b/homeassistant/components/senz/translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3.", + "no_url_available": "No hi ha cap URL disponible. Per a m\u00e9s informaci\u00f3 sobre aquest error, [consulta la secci\u00f3 d'ajuda]({docs_url})", + "oauth_error": "S'han rebut dades token inv\u00e0lides." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/de.json b/homeassistant/components/senz/translations/de.json new file mode 100644 index 00000000000..16c6d8a883d --- /dev/null +++ b/homeassistant/components/senz/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Diese Komponente ist nicht konfiguriert. Bitte in der Anleitung nachlesen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "oauth_error": "Ung\u00fcltige Token-Daten empfangen." + }, + "create_entry": { + "default": "Anmeldung erfolgreich" + }, + "step": { + "pick_implementation": { + "title": "Art der Anmeldung ausw\u00e4hlen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/el.json b/homeassistant/components/senz/translations/el.json new file mode 100644 index 00000000000..cd34896da39 --- /dev/null +++ b/homeassistant/components/senz/translations/el.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "already_in_progress": "\u0397 \u03c1\u03bf\u03ae \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7\u03c2 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7", + "authorize_url_timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2.", + "missing_configuration": "\u03a4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03b5\u03ba\u03bc\u03b7\u03c1\u03af\u03c9\u03c3\u03b7.", + "no_url_available": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL. \u0393\u03b9\u03b1 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1, [\u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1\u03c2] ( {docs_url} )", + "oauth_error": "\u039b\u03ae\u03c6\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03bf\u03cd." + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + }, + "step": { + "pick_implementation": { + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b5\u03b8\u03cc\u03b4\u03bf\u03c5 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/en.json b/homeassistant/components/senz/translations/en.json new file mode 100644 index 00000000000..bdf574691c5 --- /dev/null +++ b/homeassistant/components/senz/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured", + "already_in_progress": "Configuration flow is already in progress", + "authorize_url_timeout": "Timeout generating authorize URL.", + "missing_configuration": "The component is not configured. Please follow the documentation.", + "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", + "oauth_error": "Received invalid token data." + }, + "create_entry": { + "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/et.json b/homeassistant/components/senz/translations/et.json new file mode 100644 index 00000000000..1ea0537a1b7 --- /dev/null +++ b/homeassistant/components/senz/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni.", + "no_url_available": "URL pole saadaval. Selle t\u00f5rke kohta teabe saamiseks vaata [spikrijaotis]({docs_url})", + "oauth_error": "Saadi sobimatud loaandmed." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/fr.json b/homeassistant/components/senz/translations/fr.json new file mode 100644 index 00000000000..3190eb10b6d --- /dev/null +++ b/homeassistant/components/senz/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification expir\u00e9.", + "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide]({docs_url})", + "oauth_error": "Des donn\u00e9es de jeton non valides ont \u00e9t\u00e9 re\u00e7ues." + }, + "create_entry": { + "default": "Authentification r\u00e9ussie" + }, + "step": { + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/he.json b/homeassistant/components/senz/translations/he.json new file mode 100644 index 00000000000..fcac267c910 --- /dev/null +++ b/homeassistant/components/senz/translations/he.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "already_in_progress": "\u05d6\u05e8\u05d9\u05de\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05db\u05d1\u05e8 \u05de\u05ea\u05d1\u05e6\u05e2\u05ea", + "authorize_url_timeout": "\u05e4\u05e1\u05e7 \u05d6\u05de\u05df \u05dc\u05d9\u05e6\u05d9\u05e8\u05ea \u05db\u05ea\u05d5\u05d1\u05ea URL \u05dc\u05d0\u05d9\u05e9\u05d5\u05e8.", + "missing_configuration": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05e8\u05db\u05d9\u05d1 \u05dc\u05d0 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e0\u05d0 \u05e2\u05e7\u05d5\u05d1 \u05d0\u05d7\u05e8 \u05d4\u05ea\u05d9\u05e2\u05d5\u05d3.", + "no_url_available": "\u05d0\u05d9\u05df \u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8 \u05d6\u05de\u05d9\u05e0\u05d4. \u05e7\u05d1\u05dc\u05ea \u05de\u05d9\u05d3\u05e2 \u05e2\u05dc \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d6\u05d5, [\u05e2\u05d9\u05d9\u05df \u05d1\u05e1\u05e2\u05d9\u05e3 \u05d4\u05e2\u05d6\u05e8\u05d4] ({docs_url})", + "oauth_error": "\u05d4\u05ea\u05e7\u05d1\u05dc\u05d5 \u05e0\u05ea\u05d5\u05e0\u05d9 \u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9\u05d9\u05dd." + }, + "create_entry": { + "default": "\u05d0\u05d5\u05de\u05ea \u05d1\u05d4\u05e6\u05dc\u05d7\u05d4" + }, + "step": { + "pick_implementation": { + "title": "\u05d1\u05d7\u05e8 \u05e9\u05d9\u05d8\u05ea \u05d0\u05d9\u05de\u05d5\u05ea" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/hu.json b/homeassistant/components/senz/translations/hu.json new file mode 100644 index 00000000000..a3b07f0d3ef --- /dev/null +++ b/homeassistant/components/senz/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", + "oauth_error": "\u00c9rv\u00e9nytelen token adatok \u00e9rkeztek." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/id.json b/homeassistant/components/senz/translations/id.json new file mode 100644 index 00000000000..a2fdf8837bd --- /dev/null +++ b/homeassistant/components/senz/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "oauth_error": "Menerima respons token yang tidak valid." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/it.json b/homeassistant/components/senz/translations/it.json new file mode 100644 index 00000000000..c92ebb2a57c --- /dev/null +++ b/homeassistant/components/senz/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", + "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", + "oauth_error": "Ricevuti dati token non validi." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/ja.json b/homeassistant/components/senz/translations/ja.json new file mode 100644 index 00000000000..b6aa94ef30c --- /dev/null +++ b/homeassistant/components/senz/translations/ja.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "already_in_progress": "\u69cb\u6210\u30d5\u30ed\u30fc\u306f\u3059\u3067\u306b\u9032\u884c\u4e2d\u3067\u3059", + "authorize_url_timeout": "\u8a8d\u8a3cURL\u306e\u751f\u6210\u304c\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f\u3002", + "missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002", + "no_url_available": "\u4f7f\u7528\u53ef\u80fd\u306aURL\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30a8\u30e9\u30fc\u306e\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001[\u30d8\u30eb\u30d7\u30bb\u30af\u30b7\u30e7\u30f3\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044]({docs_url})", + "oauth_error": "\u7121\u52b9\u306a\u30c8\u30fc\u30af\u30f3\u30c7\u30fc\u30bf\u3092\u53d7\u4fe1\u3057\u307e\u3057\u305f\u3002" + }, + "create_entry": { + "default": "\u6b63\u5e38\u306b\u8a8d\u8a3c\u3055\u308c\u307e\u3057\u305f" + }, + "step": { + "pick_implementation": { + "title": "\u8a8d\u8a3c\u65b9\u6cd5\u306e\u9078\u629e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/nl.json b/homeassistant/components/senz/translations/nl.json new file mode 100644 index 00000000000..0f50cb0f918 --- /dev/null +++ b/homeassistant/components/senz/translations/nl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "oauth_error": "Ongeldige token data ontvangen." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/no.json b/homeassistant/components/senz/translations/no.json new file mode 100644 index 00000000000..6c384bb2e15 --- /dev/null +++ b/homeassistant/components/senz/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "missing_configuration": "Komponenten er ikke konfigurert. Venligst f\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgengelig. For mer informasjon om denne feilen, [sjekk hjelp seksjonen]({docs_url})", + "oauth_error": "Mottatt ugyldige token data." + }, + "create_entry": { + "default": "Vellykket Autentisering" + }, + "step": { + "pick_implementation": { + "title": "Velg Autentiserings metode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/pl.json b/homeassistant/components/senz/translations/pl.json new file mode 100644 index 00000000000..d58148cb8fa --- /dev/null +++ b/homeassistant/components/senz/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", + "no_url_available": "Brak dost\u0119pnego adresu URL. Aby uzyska\u0107 informacje na temat tego b\u0142\u0119du, [sprawd\u017a sekcj\u0119 pomocy] ({docs_url})", + "oauth_error": "Otrzymano nieprawid\u0142owe dane tokena." + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/pt-BR.json b/homeassistant/components/senz/translations/pt-BR.json new file mode 100644 index 00000000000..7e3ff2f64a9 --- /dev/null +++ b/homeassistant/components/senz/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "already_in_progress": "O fluxo de configura\u00e7\u00e3o j\u00e1 est\u00e1 em andamento", + "authorize_url_timeout": "Tempo limite gerando URL de autoriza\u00e7\u00e3o.", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "N\u00e3o h\u00e1 URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a se\u00e7\u00e3o de ajuda]({docs_url})", + "oauth_error": "Dados de token recebidos inv\u00e1lidos." + }, + "create_entry": { + "default": "Autenticado com sucesso" + }, + "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/ru.json b/homeassistant/components/senz/translations/ru.json new file mode 100644 index 00000000000..2f572831b5b --- /dev/null +++ b/homeassistant/components/senz/translations/ru.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439.", + "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435.", + "oauth_error": "\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u044b \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u043a\u0435\u043d\u0430." + }, + "create_entry": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/tr.json b/homeassistant/components/senz/translations/tr.json new file mode 100644 index 00000000000..3f6fa6f27ba --- /dev/null +++ b/homeassistant/components/senz/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "authorize_url_timeout": "Yetkilendirme URL'si olu\u015ftururken zaman a\u015f\u0131m\u0131.", + "missing_configuration": "Bile\u015fen yap\u0131land\u0131r\u0131lmam\u0131\u015f. L\u00fctfen belgeleri takip edin.", + "no_url_available": "Kullan\u0131labilir URL yok. Bu hata hakk\u0131nda bilgi i\u00e7in [yard\u0131m b\u00f6l\u00fcm\u00fcne bak\u0131n]({docs_url})", + "oauth_error": "Ge\u00e7ersiz anahtar verileri al\u0131nd\u0131." + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/senz/translations/zh-Hant.json b/homeassistant/components/senz/translations/zh-Hant.json new file mode 100644 index 00000000000..3bf08cf34c7 --- /dev/null +++ b/homeassistant/components/senz/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", + "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002", + "no_url_available": "\u6c92\u6709\u53ef\u7528\u7684\u7db2\u5740\u3002\u95dc\u65bc\u6b64\u932f\u8aa4\u66f4\u8a73\u7d30\u8a0a\u606f\uff0c[\u9ede\u9078\u5354\u52a9\u7ae0\u7bc0]({docs_url})", + "oauth_error": "\u6536\u5230\u7121\u6548\u7684\u6b0a\u6756\u8cc7\u6599\u3002" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index db8e57673b1..b5a83dfd747 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,7 +2,7 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==9.0.1"], + "requirements": ["pillow==9.1.0"], "codeowners": ["@fabaff"], "iot_class": "local_polling" } diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index cb7ec6da2d9..a34c23012cf 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -11,16 +11,8 @@ from homeassistant.components.vacuum import ( STATE_IDLE, STATE_PAUSED, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STATUS, - SUPPORT_STOP, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -31,19 +23,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, LOGGER, SHARK from .update_coordinator import SharkIqUpdateCoordinator -# Supported features -SUPPORT_SHARKIQ = ( - SUPPORT_BATTERY - | SUPPORT_FAN_SPEED - | SUPPORT_PAUSE - | SUPPORT_RETURN_HOME - | SUPPORT_START - | SUPPORT_STATE - | SUPPORT_STATUS - | SUPPORT_STOP - | SUPPORT_LOCATE -) - OPERATING_STATE_MAP = { OperatingModes.PAUSE: STATE_PAUSED, OperatingModes.START: STATE_CLEANING, @@ -87,12 +66,28 @@ async def async_setup_entry( class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuumEntity): """Shark IQ vacuum entity.""" + _attr_fan_speed_list = list(FAN_SPEEDS_MAP) + _attr_supported_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + | VacuumEntityFeature.STATUS + | VacuumEntityFeature.STOP + | VacuumEntityFeature.LOCATE + ) + def __init__( self, sharkiq: SharkIqVacuum, coordinator: SharkIqUpdateCoordinator ) -> None: """Create a new SharkVacuumEntity.""" super().__init__(coordinator) self.sharkiq = sharkiq + self._attr_name = sharkiq.name + self._attr_unique_id = sharkiq.serial_number + self._serial_number = sharkiq.serial_number def clean_spot(self, **kwargs): """Clean a spot. Not yet implemented.""" @@ -105,17 +100,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum @property def is_online(self) -> bool: """Tell us if the device is online.""" - return self.coordinator.device_is_online(self.sharkiq.serial_number) - - @property - def name(self) -> str: - """Device name.""" - return self.sharkiq.name - - @property - def serial_number(self) -> str: - """Vacuum API serial number (DSN).""" - return self.sharkiq.serial_number + return self.coordinator.device_is_online(self._serial_number) @property def model(self) -> str: @@ -128,7 +113,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum def device_info(self) -> DeviceInfo: """Device info dictionary.""" return DeviceInfo( - identifiers={(DOMAIN, self.serial_number)}, + identifiers={(DOMAIN, self._serial_number)}, manufacturer=SHARK, model=self.model, name=self.name, @@ -137,11 +122,6 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum ), ) - @property - def supported_features(self) -> int: - """Flag vacuum cleaner robot features that are supported.""" - return SUPPORT_SHARKIQ - @property def error_code(self) -> int | None: """Return the last observed error code (or None).""" @@ -178,11 +158,6 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum return STATE_DOCKED return self.operating_mode - @property - def unique_id(self) -> str: - """Return the unique id of the vacuum cleaner.""" - return self.serial_number - @property def available(self) -> bool: """Determine if the sensor is available based on API results.""" @@ -190,7 +165,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum return self.coordinator.last_update_success and self.is_online @property - def battery_level(self): + def battery_level(self) -> int | None: """Get the current battery level.""" return self.sharkiq.get_property_value(Properties.BATTERY_CAPACITY) @@ -235,11 +210,6 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum ) await self.coordinator.async_refresh() - @property - def fan_speed_list(self): - """Get the list of available fan speed steps of the vacuum cleaner.""" - return list(FAN_SPEEDS_MAP) - # Various attributes we want to expose @property def recharge_resume(self) -> bool | None: diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index 4e6767e1d61..ffd5e012ef7 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -10,14 +10,10 @@ import async_timeout from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -114,11 +110,13 @@ class BlockSleepingClimate( ): """Representation of a Shelly climate device.""" - _attr_hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_HEAT] + _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT] _attr_icon = "mdi:thermostat" _attr_max_temp = SHTRV_01_TEMPERATURE_SETTINGS["max"] _attr_min_temp = SHTRV_01_TEMPERATURE_SETTINGS["min"] - _attr_supported_features: int = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + _attr_supported_features: int = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) _attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"] _attr_temperature_unit = TEMP_CELSIUS @@ -187,14 +185,14 @@ class BlockSleepingClimate( return self.wrapper.last_update_success @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """HVAC current mode.""" if self.device_block is None: - return self.last_state.state if self.last_state else HVAC_MODE_OFF + return HVACMode(self.last_state.state) if self.last_state else HVACMode.OFF if self.device_block.mode is None or self._check_is_off(): - return HVAC_MODE_OFF + return HVACMode.OFF - return HVAC_MODE_HEAT + return HVACMode.HEAT @property def preset_mode(self) -> str | None: @@ -206,18 +204,16 @@ class BlockSleepingClimate( return self._preset_modes[cast(int, self.device_block.mode)] @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction: """HVAC current action.""" if ( self.device_block is None or self.device_block.status is None or self._check_is_off() ): - return CURRENT_HVAC_OFF + return HVACAction.OFF - return ( - CURRENT_HVAC_HEAT if bool(self.device_block.status) else CURRENT_HVAC_IDLE - ) + return HVACAction.HEATING if bool(self.device_block.status) else HVACAction.IDLE @property def preset_modes(self) -> list[str]: @@ -262,9 +258,9 @@ class BlockSleepingClimate( return await self.set_state_full_path(target_t_enabled=1, target_t=f"{current_temp}") - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set hvac mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.set_state_full_path( target_t_enabled=1, target_t=f"{self._attr_min_temp}" ) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index d0e962b5e5f..9311be1a49e 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -143,6 +143,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle the credentials step.""" errors: dict[str, str] = {} if user_input is not None: + if get_info_gen(self.info) == 2: + user_input[CONF_USERNAME] = "admin" try: device_info = await validate_input( self.hass, self.host, self.info, user_input @@ -152,8 +154,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "invalid_auth" else: errors["base"] = "cannot_connect" + except aioshelly.exceptions.InvalidAuthError: + errors["base"] = "invalid_auth" except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.exceptions.JSONRPCError: + errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -171,15 +177,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): else: user_input = {} - schema = vol.Schema( - { + if get_info_gen(self.info) == 2: + schema = { + vol.Required(CONF_PASSWORD, default=user_input.get(CONF_PASSWORD)): str, + } + else: + schema = { vol.Required(CONF_USERNAME, default=user_input.get(CONF_USERNAME)): str, vol.Required(CONF_PASSWORD, default=user_input.get(CONF_PASSWORD)): str, } - ) return self.async_show_form( - step_id="credentials", data_schema=schema, errors=errors + step_id="credentials", data_schema=vol.Schema(schema), errors=errors ) async def async_step_zeroconf( @@ -198,7 +207,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured({CONF_HOST: host}) self.host = host - self.context["title_placeholders"] = {"name": discovery_info.name.split(".")[0]} + self.context.update( + { + "title_placeholders": {"name": discovery_info.name.split(".")[0]}, + "configuration_url": f"http://{discovery_info.host}", + } + ) if get_info_auth(self.info): return await self.async_step_credentials() diff --git a/homeassistant/components/shelly/cover.py b/homeassistant/components/shelly/cover.py index 79ee15ee5ce..9bc61f9415f 100644 --- a/homeassistant/components/shelly/cover.py +++ b/homeassistant/components/shelly/cover.py @@ -7,12 +7,9 @@ from aioshelly.block_device import Block from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -76,9 +73,11 @@ class BlockShellyCover(ShellyBlockEntity, CoverEntity): """Initialize block cover.""" super().__init__(wrapper, block) self.control_result: dict[str, Any] | None = None - self._attr_supported_features: int = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + self._attr_supported_features: int = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP + ) if self.wrapper.device.settings["rollers"][0]["positioning"]: - self._attr_supported_features |= SUPPORT_SET_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_POSITION @property def is_closed(self) -> bool: @@ -150,9 +149,11 @@ class RpcShellyCover(ShellyRpcEntity, CoverEntity): """Initialize rpc cover.""" super().__init__(wrapper, f"cover:{id_}") self._id = id_ - self._attr_supported_features: int = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + self._attr_supported_features: int = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP + ) if self.status["pos_control"]: - self._attr_supported_features |= SUPPORT_SET_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_POSITION @property def is_closed(self) -> bool | None: diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index e86c53c1914..3530241f102 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -12,14 +12,9 @@ from homeassistant.components.light import ( ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_TRANSITION, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_ONOFF, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, brightness_supported, ) from homeassistant.config_entries import ConfigEntry @@ -148,21 +143,21 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): self._attr_max_mireds = MIRED_MAX_VALUE_COLOR self._min_kelvin = KELVIN_MIN_VALUE_COLOR if wrapper.model in RGBW_MODELS: - self._attr_supported_color_modes.add(COLOR_MODE_RGBW) + self._attr_supported_color_modes.add(ColorMode.RGBW) else: - self._attr_supported_color_modes.add(COLOR_MODE_RGB) + self._attr_supported_color_modes.add(ColorMode.RGB) if hasattr(block, "colorTemp"): - self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) if not self._attr_supported_color_modes: if hasattr(block, "brightness") or hasattr(block, "gain"): - self._attr_supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) else: - self._attr_supported_color_modes.add(COLOR_MODE_ONOFF) + self._attr_supported_color_modes.add(ColorMode.ONOFF) if hasattr(block, "effect"): - self._attr_supported_features |= SUPPORT_EFFECT + self._attr_supported_features |= LightEntityFeature.EFFECT if wrapper.model in MODELS_SUPPORTING_LIGHT_TRANSITION: match = FIRMWARE_PATTERN.search(wrapper.device.settings.get("fw", "")) @@ -170,7 +165,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): match is not None and int(match[0]) >= LIGHT_TRANSITION_MIN_FIRMWARE_DATE ): - self._attr_supported_features |= SUPPORT_TRANSITION + self._attr_supported_features |= LightEntityFeature.TRANSITION @property def is_on(self) -> bool: @@ -215,20 +210,20 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): return round(255 * brightness_pct / 100) @property - def color_mode(self) -> str: + def color_mode(self) -> ColorMode: """Return the color mode of the light.""" if self.mode == "color": if hasattr(self.block, "white"): - return COLOR_MODE_RGBW - return COLOR_MODE_RGB + return ColorMode.RGBW + return ColorMode.RGB if hasattr(self.block, "colorTemp"): - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP if hasattr(self.block, "brightness") or hasattr(self.block, "gain"): - return COLOR_MODE_BRIGHTNESS + return ColorMode.BRIGHTNESS - return COLOR_MODE_ONOFF + return ColorMode.ONOFF @property def rgb_color(self) -> tuple[int, int, int]: @@ -268,7 +263,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): @property def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" - if not self.supported_features & SUPPORT_EFFECT: + if not self.supported_features & LightEntityFeature.EFFECT: return None if self.wrapper.model == "SHBLB-1": @@ -279,7 +274,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): @property def effect(self) -> str | None: """Return the current effect.""" - if not self.supported_features & SUPPORT_EFFECT: + if not self.supported_features & LightEntityFeature.EFFECT: return None if self.control_result: @@ -315,19 +310,19 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): if hasattr(self.block, "brightness"): params["brightness"] = brightness_pct - if ATTR_COLOR_TEMP in kwargs and COLOR_MODE_COLOR_TEMP in supported_color_modes: + if ATTR_COLOR_TEMP in kwargs and ColorMode.COLOR_TEMP in supported_color_modes: color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) # Color temperature change - used only in white mode, switch device mode to white set_mode = "white" params["temp"] = int(color_temp) - if ATTR_RGB_COLOR in kwargs and COLOR_MODE_RGB in supported_color_modes: + if ATTR_RGB_COLOR in kwargs and ColorMode.RGB in supported_color_modes: # Color channels change - used only in color mode, switch device mode to color set_mode = "color" (params["red"], params["green"], params["blue"]) = kwargs[ATTR_RGB_COLOR] - if ATTR_RGBW_COLOR in kwargs and COLOR_MODE_RGBW in supported_color_modes: + if ATTR_RGBW_COLOR in kwargs and ColorMode.RGBW in supported_color_modes: # Color channels change - used only in color mode, switch device mode to color set_mode = "color" (params["red"], params["green"], params["blue"], params["white"]) = kwargs[ diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 1d4d47748be..a9aade00933 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==1.0.11"], + "requirements": ["aioshelly==2.0.0"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 92baec1e42b..3afe8052e03 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,7 +2,7 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==9.0.1", "simplehound==0.3"], + "requirements": ["pillow==9.1.0", "simplehound==0.3"], "codeowners": ["@robmarkcole"], "iot_class": "cloud_polling", "loggers": ["simplehound"] diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 77732f4e2cb..c74efab61ac 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -51,6 +51,7 @@ from homeassistant.const import ( ATTR_DEVICE_ID, CONF_CODE, CONF_TOKEN, + CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, Platform, ) @@ -90,7 +91,6 @@ from .const import ( ATTR_EXIT_DELAY_HOME, ATTR_LIGHT, ATTR_VOICE_PROMPT_VOLUME, - CONF_USER_ID, DOMAIN, LOGGER, ) @@ -280,11 +280,13 @@ def _async_standardize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> raise ConfigEntryAuthFailed( "New SimpliSafe OAuth standard requires re-authentication" ) + if CONF_USERNAME not in entry.data: + raise ConfigEntryAuthFailed("Need to re-auth with username/password") entry_updates = {} if not entry.unique_id: # If the config entry doesn't already have a unique ID, set one: - entry_updates["unique_id"] = entry.data[CONF_USER_ID] + entry_updates["unique_id"] = entry.data[CONF_USERNAME] if CONF_CODE in entry.data: # If an alarm code was provided as part of configuration.yaml, pop it out of # the config entry's data and move it to options: @@ -598,7 +600,7 @@ class SimpliSafe: self.coordinator = DataUpdateCoordinator( self._hass, LOGGER, - name=self.entry.data[CONF_USER_ID], + name=self.entry.title, update_interval=DEFAULT_SCAN_INTERVAL, update_method=self.async_update, ) diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index cf896c3a320..dc670393f18 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -20,13 +20,9 @@ from simplipy.websocket import ( ) from homeassistant.components.alarm_control_panel import ( - FORMAT_NUMBER, - FORMAT_TEXT, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -118,6 +114,11 @@ async def async_setup_entry( class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): """Representation of a SimpliSafe alarm.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) + def __init__(self, simplisafe: SimpliSafe, system: SystemType) -> None: """Initialize the SimpliSafe alarm.""" super().__init__( @@ -128,10 +129,10 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): if code := self._simplisafe.entry.options.get(CONF_CODE): if code.isdigit(): - self._attr_code_format = FORMAT_NUMBER + self._attr_code_format = CodeFormat.NUMBER else: - self._attr_code_format = FORMAT_TEXT - self._attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY + self._attr_code_format = CodeFormat.TEXT + self._last_event = None self._set_state_from_system_data() diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index ad6e01f0422..926f904a912 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -1,56 +1,47 @@ """Config flow to configure the SimpliSafe component.""" from __future__ import annotations -from typing import Any, NamedTuple +import asyncio +from typing import Any +import async_timeout from simplipy import API -from simplipy.errors import InvalidCredentialsError, SimplipyError -from simplipy.util.auth import ( - get_auth0_code_challenge, - get_auth0_code_verifier, - get_auth_url, -) +from simplipy.api import AuthStates +from simplipy.errors import InvalidCredentialsError, SimplipyError, Verify2FAPending import voluptuous as vol from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_CODE, CONF_TOKEN, CONF_URL, CONF_USERNAME +from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv -from .const import CONF_USER_ID, DOMAIN, LOGGER +from .const import DOMAIN, LOGGER -CONF_AUTH_CODE = "auth_code" -CONF_DOCS_URL = "docs_url" +DEFAULT_EMAIL_2FA_SLEEP = 3 +DEFAULT_EMAIL_2FA_TIMEOUT = 300 -AUTH_DOCS_URL = ( - "http://home-assistant.io/integrations/simplisafe#getting-an-authorization-code" +STEP_REAUTH_SCHEMA = vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + } +) + +STEP_SMS_2FA_SCHEMA = vol.Schema( + { + vol.Required(CONF_CODE): cv.string, + } ) STEP_USER_SCHEMA = vol.Schema( { - vol.Required(CONF_AUTH_CODE): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, } ) -class SimpliSafeOAuthValues(NamedTuple): - """Define a named tuple to handle SimpliSafe OAuth strings.""" - - code_verifier: str - auth_url: str - - -@callback -def async_get_simplisafe_oauth_values() -> SimpliSafeOAuthValues: - """Get a SimpliSafe OAuth code verifier and auth URL.""" - code_verifier = get_auth0_code_verifier() - code_challenge = get_auth0_code_challenge(code_verifier) - auth_url = get_auth_url(code_challenge) - return SimpliSafeOAuthValues(code_verifier, auth_url) - - class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a SimpliSafe config flow.""" @@ -58,10 +49,46 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._oauth_values: SimpliSafeOAuthValues | None = None + self._email_2fa_task: asyncio.Task | None = None + self._password: str | None = None self._reauth: bool = False + self._simplisafe: API | None = None self._username: str | None = None + async def _async_authenticate( + self, originating_step_id: str, originating_step_schema: vol.Schema + ) -> FlowResult: + """Attempt to authenticate to the SimpliSafe API.""" + assert self._password + assert self._username + + errors = {} + session = aiohttp_client.async_get_clientsession(self.hass) + + try: + self._simplisafe = await API.async_from_credentials( + self._username, self._password, session=session + ) + except InvalidCredentialsError: + errors = {"base": "invalid_auth"} + except SimplipyError as err: + LOGGER.error("Unknown error while logging into SimpliSafe: %s", err) + errors = {"base": "unknown"} + + if errors: + return self.async_show_form( + step_id=originating_step_id, + data_schema=originating_step_schema, + errors=errors, + description_placeholders={CONF_USERNAME: self._username}, + ) + + assert self._simplisafe + + if self._simplisafe.auth_state == AuthStates.PENDING_2FA_SMS: + return await self.async_step_sms_2fa() + return await self.async_step_email_2fa() + @staticmethod @callback def async_get_options_flow( @@ -70,77 +97,146 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Define the config flow to handle options.""" return SimpliSafeOptionsFlowHandler(config_entry) - def _async_show_form(self, *, errors: dict[str, Any] | None = None) -> FlowResult: - """Show the form.""" - self._oauth_values = async_get_simplisafe_oauth_values() - - return self.async_show_form( - step_id="user", - data_schema=STEP_USER_SCHEMA, - errors=errors or {}, - description_placeholders={ - CONF_URL: self._oauth_values.auth_url, - CONF_DOCS_URL: AUTH_DOCS_URL, - }, - ) - async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: """Handle configuration by re-auth.""" - self._username = config.get(CONF_USERNAME) self._reauth = True - return await self.async_step_user() + + if CONF_USERNAME not in config: + # Old versions of the config flow may not have the username by this point; + # in that case, we reauth them by making them go through the user flow: + return await self.async_step_user() + + self._username = config[CONF_USERNAME] + return await self.async_step_reauth_confirm() + + async def _async_get_email_2fa(self) -> None: + """Define a task to wait for email-based 2FA.""" + assert self._simplisafe + + try: + async with async_timeout.timeout(DEFAULT_EMAIL_2FA_TIMEOUT): + while True: + try: + await self._simplisafe.async_verify_2fa_email() + except Verify2FAPending: + LOGGER.info("Email-based 2FA pending; trying again") + await asyncio.sleep(DEFAULT_EMAIL_2FA_SLEEP) + else: + break + finally: + self.hass.async_create_task( + self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) + ) + + async def async_step_email_2fa( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle email-based two-factor authentication.""" + if not self._email_2fa_task: + self._email_2fa_task = self.hass.async_create_task( + self._async_get_email_2fa() + ) + return self.async_show_progress( + step_id="email_2fa", progress_action="email_2fa" + ) + + try: + await self._email_2fa_task + except asyncio.TimeoutError: + return self.async_show_progress_done(next_step_id="email_2fa_error") + return self.async_show_progress_done(next_step_id="finish") + + async def async_step_email_2fa_error( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle an error during email-based two-factor authentication.""" + return self.async_abort(reason="email_2fa_timed_out") + + async def async_step_finish( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the final step.""" + assert self._simplisafe + assert self._username + + data = { + CONF_USERNAME: self._username, + CONF_TOKEN: self._simplisafe.refresh_token, + } + + user_id = str(self._simplisafe.user_id) + + if self._reauth: + # "Old" config entries utilized the user's email address (username) as the + # unique ID, whereas "new" config entries utilize the SimpliSafe user ID – + # only one can exist at a time, but the presence of either one is a + # candidate for re-auth: + if existing_entries := [ + entry + for entry in self.hass.config_entries.async_entries() + if entry.domain == DOMAIN + and entry.unique_id in (self._username, user_id) + ]: + existing_entry = existing_entries[0] + self.hass.config_entries.async_update_entry( + existing_entry, unique_id=user_id, title=self._username, data=data + ) + self.hass.async_create_task( + self.hass.config_entries.async_reload(existing_entry.entry_id) + ) + return self.async_abort(reason="reauth_successful") + + await self.async_set_unique_id(user_id) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=self._username, data=data) + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle re-auth completion.""" + if not user_input: + return self.async_show_form( + step_id="reauth_confirm", + data_schema=STEP_REAUTH_SCHEMA, + description_placeholders={CONF_USERNAME: self._username}, + ) + + self._password = user_input[CONF_PASSWORD] + return await self._async_authenticate("reauth_confirm", STEP_REAUTH_SCHEMA) + + async def async_step_sms_2fa( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle SMS-based two-factor authentication.""" + if not user_input: + return self.async_show_form( + step_id="sms_2fa", + data_schema=STEP_SMS_2FA_SCHEMA, + ) + + assert self._simplisafe + + try: + await self._simplisafe.async_verify_2fa_sms(user_input[CONF_CODE]) + except InvalidCredentialsError: + return self.async_show_form( + step_id="sms_2fa", + data_schema=STEP_SMS_2FA_SCHEMA, + errors={CONF_CODE: "invalid_auth"}, + ) + + return await self.async_step_finish() async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the start of the config flow.""" if user_input is None: - return self._async_show_form() + return self.async_show_form(step_id="user", data_schema=STEP_USER_SCHEMA) - assert self._oauth_values - - errors = {} - session = aiohttp_client.async_get_clientsession(self.hass) - - try: - simplisafe = await API.async_from_auth( - user_input[CONF_AUTH_CODE], - self._oauth_values.code_verifier, - session=session, - ) - except InvalidCredentialsError: - errors = {"base": "invalid_auth"} - except SimplipyError as err: - LOGGER.error("Unknown error while logging into SimpliSafe: %s", err) - errors = {"base": "unknown"} - - if errors: - return self._async_show_form(errors=errors) - - data = {CONF_USER_ID: simplisafe.user_id, CONF_TOKEN: simplisafe.refresh_token} - unique_id = str(simplisafe.user_id) - - if self._reauth: - # "Old" config entries utilized the user's email address (username) as the - # unique ID, whereas "new" config entries utilize the SimpliSafe user ID – - # either one is a candidate for re-auth: - existing_entry = await self.async_set_unique_id(self._username or unique_id) - if not existing_entry: - # If we don't have an entry that matches this user ID, the user logged - # in with different credentials: - return self.async_abort(reason="wrong_account") - - self.hass.config_entries.async_update_entry( - existing_entry, unique_id=unique_id, data=data - ) - self.hass.async_create_task( - self.hass.config_entries.async_reload(existing_entry.entry_id) - ) - return self.async_abort(reason="reauth_successful") - - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() - return self.async_create_entry(title=unique_id, data=data) + self._username = user_input[CONF_USERNAME] + self._password = user_input[CONF_PASSWORD] + return await self._async_authenticate("user", STEP_USER_SCHEMA) class SimpliSafeOptionsFlowHandler(config_entries.OptionsFlow): diff --git a/homeassistant/components/simplisafe/const.py b/homeassistant/components/simplisafe/const.py index 658ddfc13a6..1405f60b400 100644 --- a/homeassistant/components/simplisafe/const.py +++ b/homeassistant/components/simplisafe/const.py @@ -14,5 +14,3 @@ ATTR_EXIT_DELAY_AWAY = "exit_delay_away" ATTR_EXIT_DELAY_HOME = "exit_delay_home" ATTR_LIGHT = "light" ATTR_VOICE_PROMPT_VOLUME = "voice_prompt_volume" - -CONF_USER_ID = "user_id" diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 3791a9cace9..804523b3390 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==2022.03.0"], + "requirements": ["simplisafe-python==2022.05.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/homeassistant/components/simplisafe/strings.json b/homeassistant/components/simplisafe/strings.json index a0ff28fd689..85e579fd455 100644 --- a/homeassistant/components/simplisafe/strings.json +++ b/homeassistant/components/simplisafe/strings.json @@ -1,23 +1,38 @@ { "config": { "step": { - "user": { - "description": "SimpliSafe authenticates with Home Assistant via the SimpliSafe web app. Due to technical limitations, there is a manual step at the end of this process; please ensure that you read the [documentation]({docs_url}) before starting.\n\n1. Click [here]({url}) to open the SimpliSafe web app and input your credentials.\n\n2. When the login process is complete, return here and input the authorization code below.", + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "Please re-enter the password for {username}.", "data": { - "auth_code": "Authorization Code" + "password": "[%key:common::config_flow::data::password%]" + } + }, + "sms_2fa": { + "description": "Input the two-factor authentication code sent to you via SMS.", + "data": { + "code": "Code" + } + }, + "user": { + "description": "Input your username and password.", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" } } }, "error": { - "identifier_exists": "Account already registered", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "still_awaiting_mfa": "Still awaiting MFA email click", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { "already_configured": "This SimpliSafe account is already in use.", - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", - "wrong_account": "The user credentials provided do not match this SimpliSafe account." + "email_2fa_timed_out": "Timed out while waiting for email-based two-factor authentication.", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + }, + "progress": { + "email_2fa": "Check your email for a verification link from Simplisafe." } }, "options": { diff --git a/homeassistant/components/simplisafe/translations/bg.json b/homeassistant/components/simplisafe/translations/bg.json index 21cdaabf9a2..9d32e18ae5c 100644 --- a/homeassistant/components/simplisafe/translations/bg.json +++ b/homeassistant/components/simplisafe/translations/bg.json @@ -9,11 +9,6 @@ "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "\u041a\u043e\u0434 \u0437\u0430 \u043e\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" - } - }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" diff --git a/homeassistant/components/simplisafe/translations/ca.json b/homeassistant/components/simplisafe/translations/ca.json index 3ab643b534f..7c590a052b1 100644 --- a/homeassistant/components/simplisafe/translations/ca.json +++ b/homeassistant/components/simplisafe/translations/ca.json @@ -12,15 +12,7 @@ "unknown": "Error inesperat" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Codi d'autoritzaci\u00f3" - }, - "description": "Introdueix el codi d'autoritzaci\u00f3 de l'URL de l'aplicaci\u00f3 web SimpliSafe:", - "title": "Acabament d'autoritzaci\u00f3" - }, "mfa": { - "description": "Consulta el correu i busca-hi un missatge amb un enlla\u00e7 de SimpliSafe. Despr\u00e9s de verificar l'enlla\u00e7, torna aqu\u00ed per completar la instal\u00b7laci\u00f3 de la integraci\u00f3.", "title": "Autenticaci\u00f3 multi-factor SimpliSafe" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/cs.json b/homeassistant/components/simplisafe/translations/cs.json index bbb47121fbc..152c0282216 100644 --- a/homeassistant/components/simplisafe/translations/cs.json +++ b/homeassistant/components/simplisafe/translations/cs.json @@ -11,7 +11,6 @@ }, "step": { "mfa": { - "description": "Zkontrolujte, zda v\u00e1m do e-mail p\u0159i\u0161el odkaz od SimpliSafe. Po ov\u011b\u0159en\u00ed odkazu se vra\u0165te sem a dokon\u010dete instalaci integrace.", "title": "V\u00edcefaktorov\u00e9 ov\u011b\u0159ov\u00e1n\u00ed SimpliSafe" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/de.json b/homeassistant/components/simplisafe/translations/de.json index 1ee4802e77f..51083565770 100644 --- a/homeassistant/components/simplisafe/translations/de.json +++ b/homeassistant/components/simplisafe/translations/de.json @@ -6,38 +6,37 @@ "wrong_account": "Die angegebenen Benutzeranmeldeinformationen stimmen nicht mit diesem SimpliSafe-Konto \u00fcberein." }, "error": { + "2fa_timed_out": "Zeit\u00fcberschreitung beim Warten auf Zwei-Faktor-Authentifizierung", "identifier_exists": "Konto bereits registriert", "invalid_auth": "Ung\u00fcltige Authentifizierung", "still_awaiting_mfa": "Immernoch warten auf MFA-E-Mail-Klick", "unknown": "Unerwarteter Fehler" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Autorisierungscode" - }, - "description": "Gib den Autorisierungscode von der URL der SimpliSafe-Webanwendung ein:", - "title": "Autorisierung abschlie\u00dfen" - }, "mfa": { - "description": "Pr\u00fcfe deine E-Mail auf einen Link von SimpliSafe. Kehre nach der Verifizierung des Links hierher zur\u00fcck, um die Installation der Integration abzuschlie\u00dfen.", "title": "SimpliSafe Multi-Faktor-Authentifizierung" }, "reauth_confirm": { "data": { "password": "Passwort" }, - "description": "Dein Zugriffstoken ist abgelaufen oder wurde widerrufen. Gib dein Passwort ein, um dein Konto erneut zu verkn\u00fcpfen.", + "description": "Bitte gib das Passwort f\u00fcr {username} erneut ein.", "title": "Integration erneut authentifizieren" }, + "sms_2fa": { + "data": { + "code": "Code" + }, + "description": "Gib den Code f\u00fcr die Zwei-Faktor-Authentifizierung ein, den du per SMS erhalten hast." + }, "user": { "data": { "auth_code": "Autorisierungscode", "code": "Code (wird in der Benutzeroberfl\u00e4che von Home Assistant verwendet)", "password": "Passwort", - "username": "E-Mail" + "username": "Benutzername" }, - "description": "SimpliSafe authentifiziert sich bei Home Assistant \u00fcber die SimpliSafe Web-App. Aufgrund technischer Beschr\u00e4nkungen gibt es am Ende dieses Prozesses einen manuellen Schritt; bitte stelle sicher, dass du die [Dokumentation]({docs_url}) liest, bevor du beginnst.\n\n1. Klicke [hier]({url}), um die SimpliSafe-Webanwendung zu \u00f6ffnen und deine Anmeldedaten einzugeben.\n\n2. Wenn der Anmeldevorgang abgeschlossen ist, kehre hierher zur\u00fcck und gib unten stehenden Autorisierungscode ein.", + "description": "Gib deinen Benutzernamen und Passwort ein.", "title": "Gib deine Informationen ein" } } diff --git a/homeassistant/components/simplisafe/translations/el.json b/homeassistant/components/simplisafe/translations/el.json index d35c59bcc40..880097c6cef 100644 --- a/homeassistant/components/simplisafe/translations/el.json +++ b/homeassistant/components/simplisafe/translations/el.json @@ -6,21 +6,14 @@ "wrong_account": "\u03a4\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc SimpliSafe." }, "error": { + "2fa_timed_out": "\u0388\u03bb\u03b7\u03be\u03b5 \u03c4\u03bf \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03bc\u03bf\u03bd\u03ae \u03b3\u03b9\u03b1 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd", "identifier_exists": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ae\u03b4\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2", "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "still_awaiting_mfa": "\u0391\u03bd\u03b1\u03bc\u03ad\u03bd\u03b5\u03c4\u03b1\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf email \u03c4\u03bf\u03c5 \u03a5\u03c0\u03bf\u03c5\u03c1\u03b3\u03b5\u03af\u03bf\u03c5 \u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03b9\u03ba\u03ce\u03bd", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2" - }, - "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 SimpliSafe web:", - "title": "\u039f\u03bb\u03bf\u03ba\u03bb\u03ae\u03c1\u03c9\u03c3\u03b7 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2" - }, "mfa": { - "description": "\u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf email \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd SimpliSafe. \u0391\u03c6\u03bf\u03cd \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.", "title": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd SimpliSafe" }, "reauth_confirm": { @@ -30,6 +23,12 @@ "description": "\u0397 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae \u03c3\u03b1\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ae\u03be\u03b5\u03b9 \u03ae \u03b1\u03bd\u03b1\u03ba\u03bb\u03b7\u03b8\u03b5\u03af. \u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2.", "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" }, + "sms_2fa": { + "data": { + "code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd \u03c0\u03bf\u03c5 \u03c3\u03b1\u03c2 \u03b5\u03c3\u03c4\u03ac\u03bb\u03b7 \u03bc\u03ad\u03c3\u03c9 SMS." + }, "user": { "data": { "auth_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03be\u03bf\u03c5\u03c3\u03b9\u03bf\u03b4\u03cc\u03c4\u03b7\u03c3\u03b7\u03c2", diff --git a/homeassistant/components/simplisafe/translations/en.json b/homeassistant/components/simplisafe/translations/en.json index a3481ecf2c7..0da6f6442e4 100644 --- a/homeassistant/components/simplisafe/translations/en.json +++ b/homeassistant/components/simplisafe/translations/en.json @@ -2,43 +2,36 @@ "config": { "abort": { "already_configured": "This SimpliSafe account is already in use.", - "reauth_successful": "Re-authentication was successful", - "wrong_account": "The user credentials provided do not match this SimpliSafe account." + "email_2fa_timed_out": "Timed out while waiting for email-based two-factor authentication.", + "reauth_successful": "Re-authentication was successful" }, "error": { - "identifier_exists": "Account already registered", "invalid_auth": "Invalid authentication", - "still_awaiting_mfa": "Still awaiting MFA email click", "unknown": "Unexpected error" }, + "progress": { + "email_2fa": "Check your email for a verification link from Simplisafe." + }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Authorization Code" - }, - "description": "Input the authorization code from the SimpliSafe web app URL:", - "title": "Finish Authorization" - }, - "mfa": { - "description": "Check your email for a link from SimpliSafe. After verifying the link, return here to complete the installation of the integration.", - "title": "SimpliSafe Multi-Factor Authentication" - }, "reauth_confirm": { "data": { "password": "Password" }, - "description": "Your access has expired or been revoked. Enter your password to re-link your account.", + "description": "Please re-enter the password for {username}.", "title": "Reauthenticate Integration" }, + "sms_2fa": { + "data": { + "code": "Code" + }, + "description": "Input the two-factor authentication code sent to you via SMS." + }, "user": { "data": { - "auth_code": "Authorization Code", - "code": "Code (used in Home Assistant UI)", "password": "Password", - "username": "Email" + "username": "Username" }, - "description": "SimpliSafe authenticates with Home Assistant via the SimpliSafe web app. Due to technical limitations, there is a manual step at the end of this process; please ensure that you read the [documentation]({docs_url}) before starting.\n\n1. Click [here]({url}) to open the SimpliSafe web app and input your credentials.\n\n2. When the login process is complete, return here and input the authorization code below.", - "title": "Fill in your information." + "description": "Input your username and password." } } }, diff --git a/homeassistant/components/simplisafe/translations/es.json b/homeassistant/components/simplisafe/translations/es.json index 690e01206d3..d4b066890f1 100644 --- a/homeassistant/components/simplisafe/translations/es.json +++ b/homeassistant/components/simplisafe/translations/es.json @@ -12,15 +12,7 @@ "unknown": "Error inesperado" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "C\u00f3digo de Autorizaci\u00f3n" - }, - "description": "Introduzca el c\u00f3digo de autorizaci\u00f3n desde la URL de la aplicaci\u00f3n web de SimpliSafe:", - "title": "Terminar Autorizaci\u00f3n" - }, "mfa": { - "description": "Comprueba tu correo electr\u00f3nico para obtener un enlace desde SimpliSafe. Despu\u00e9s de verificar el enlace, vulve aqu\u00ed para completar la instalaci\u00f3n de la integraci\u00f3n.", "title": "Autenticaci\u00f3n Multi-Factor SimpliSafe" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/et.json b/homeassistant/components/simplisafe/translations/et.json index 97e59415123..83c441de9f9 100644 --- a/homeassistant/components/simplisafe/translations/et.json +++ b/homeassistant/components/simplisafe/translations/et.json @@ -6,38 +6,37 @@ "wrong_account": "Esitatud kasutaja mandaadid ei \u00fchti selle SimpliSafe kontoga." }, "error": { + "2fa_timed_out": "Kahefaktoriline autentimine aegus", "identifier_exists": "Konto on juba registreeritud", "invalid_auth": "Tuvastamise viga", "still_awaiting_mfa": "Ootan endiselt MFA e-posti klikki", "unknown": "Tundmatu viga" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Tuvastuskood" - }, - "description": "Sisesta tuvastuskood SimpliSafe veebirakenduse URL-ist:", - "title": "L\u00f5peta tuvastamine" - }, "mfa": { - "description": "Kontrolli oma e-posti: link SimpliSafe-lt. P\u00e4rast lingi kontrollimist naase siia, et viia l\u00f5pule sidumise installimine.", "title": "SimpliSafe mitmeastmeline autentimine" }, "reauth_confirm": { "data": { "password": "Salas\u00f5na" }, - "description": "Juurdep\u00e4\u00e4suluba on aegunud v\u00f5i on see t\u00fchistatud. Konto taassidumiseks sisesta salas\u00f5na.", + "description": "Taassisesta salas\u00f5na kasutajanimele {username}.", "title": "Taastuvasta SimpliSafe'i konto" }, + "sms_2fa": { + "data": { + "code": "Kood" + }, + "description": "Sisesta SMS-iga saadetud kahefaktoriline autentimiskood." + }, "user": { "data": { "auth_code": "Tuvastuskood", "code": "Kood (kasutatakse Home Assistant'i kasutajaliideses)", "password": "Salas\u00f5na", - "username": "E-post" + "username": "Kasutajanimi" }, - "description": "SimpliSafe autendib Home Assistantiga SimpliSafe'i veebirakenduse kaudu. Tehniliste piirangute t\u00f5ttu on selle protsessi l\u00f5pus k\u00e4sitsi samm; enne alustamist loe kindlasti [dokumentatsioon]( {docs_url} \n\n 1. SimpliSafe'i veebirakenduse avamiseks ja oma mandaatide sisestamiseks kl\u00f5psa [siin]( {url} \n\n 2. Kui sisselogimisprotsess on l\u00f5ppenud, naase siia ja sisesta alltoodud tuvastuskood.", + "description": "Sisesta kasutajatunnus ja salas\u00f5na", "title": "Sisesta oma teave." } } diff --git a/homeassistant/components/simplisafe/translations/fr.json b/homeassistant/components/simplisafe/translations/fr.json index d50ac11f851..5ed837d405a 100644 --- a/homeassistant/components/simplisafe/translations/fr.json +++ b/homeassistant/components/simplisafe/translations/fr.json @@ -6,38 +6,37 @@ "wrong_account": "Les informations d'identification d'utilisateur fournies ne correspondent pas \u00e0 ce compte SimpliSafe." }, "error": { + "2fa_timed_out": "D\u00e9lai d'attente de l'authentification \u00e0 deux facteurs expir\u00e9", "identifier_exists": "Compte d\u00e9j\u00e0 enregistr\u00e9", "invalid_auth": "Authentification non valide", "still_awaiting_mfa": "En attente de clic sur le message \u00e9lectronique d'authentification multi facteur", "unknown": "Erreur inattendue" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Code d'autorisation" - }, - "description": "Saisissez le code d'autorisation \u00e0 partir de l'URL de l'application Web SimpliSafe\u00a0:", - "title": "Terminer l'autorisation" - }, "mfa": { - "description": "V\u00e9rifiez votre messagerie pour un lien de SimpliSafe. Apr\u00e8s avoir v\u00e9rifi\u00e9 le lien, revenez ici pour terminer l'installation de l'int\u00e9gration.", "title": "Authentification multi facteur SimpliSafe" }, "reauth_confirm": { "data": { "password": "Mot de passe" }, - "description": "Votre jeton d'acc\u00e8s a expir\u00e9 ou a \u00e9t\u00e9 r\u00e9voqu\u00e9. Entrez votre mot de passe pour r\u00e9 associer votre compte.", + "description": "Veuillez de nouveau saisir le mot de passe pour {username}.", "title": "R\u00e9-authentifier l'int\u00e9gration" }, + "sms_2fa": { + "data": { + "code": "Code" + }, + "description": "Saisissez le code d'authentification \u00e0 deux facteurs qui vous a \u00e9t\u00e9 envoy\u00e9 par SMS." + }, "user": { "data": { "auth_code": "Code d'autorisation", "code": "Code (utilis\u00e9 dans l'interface Home Assistant)", "password": "Mot de passe", - "username": "Courriel" + "username": "Nom d'utilisateur" }, - "description": "SimpliSafe s'authentifie avec Home Assistant via l'application Web SimpliSafe. En raison de limitations techniques, il y a une \u00e9tape manuelle \u00e0 la fin de ce processus ; veuillez vous assurer de lire la [documentation]( {docs_url} ) avant de commencer. \n\n 1. Cliquez sur [ici]( {url} ) pour ouvrir l'application Web SimpliSafe et saisissez vos informations d'identification. \n\n 2. Une fois le processus de connexion termin\u00e9, revenez ici et saisissez le code d'autorisation ci-dessous.", + "description": "Saisissez votre nom d'utilisateur et votre mot de passe.", "title": "Veuillez saisir vos informations" } } diff --git a/homeassistant/components/simplisafe/translations/hu.json b/homeassistant/components/simplisafe/translations/hu.json index 69881266c4f..e1b8769f4b6 100644 --- a/homeassistant/components/simplisafe/translations/hu.json +++ b/homeassistant/components/simplisafe/translations/hu.json @@ -6,38 +6,37 @@ "wrong_account": "A megadott felhaszn\u00e1l\u00f3i hiteles\u00edt\u0151 adatok nem j\u00f3k ehhez a SimpliSafe fi\u00f3khoz." }, "error": { + "2fa_timed_out": "A k\u00e9tfaktoros hiteles\u00edt\u00e9sre val\u00f3 v\u00e1rakoz\u00e1s ideje lej\u00e1rt", "identifier_exists": "Fi\u00f3k m\u00e1r regisztr\u00e1lva van", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "still_awaiting_mfa": "M\u00e9g v\u00e1r az MFA e-mail kattint\u00e1sra", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Enged\u00e9lyez\u00e9si k\u00f3d" - }, - "description": "Adja meg a SimpliSafe webes alkalmaz\u00e1s URL-c\u00edm\u00e9n tal\u00e1lhat\u00f3 enged\u00e9lyez\u00e9si k\u00f3dot:", - "title": "Enged\u00e9lyez\u00e9s befejez\u00e9se" - }, "mfa": { - "description": "Ellen\u0151rizze e-mailj\u00e9ben a SimpliSafe linkj\u00e9t. A link ellen\u0151rz\u00e9se ut\u00e1n t\u00e9rjen vissza ide, \u00e9s fejezze be az integr\u00e1ci\u00f3 telep\u00edt\u00e9s\u00e9t.", "title": "SimpliSafe t\u00f6bbt\u00e9nyez\u0151s hiteles\u00edt\u00e9s" }, "reauth_confirm": { "data": { "password": "Jelsz\u00f3" }, - "description": "Hozz\u00e1f\u00e9r\u00e9se lej\u00e1rt vagy visszavont\u00e1k. Adja meg jelszav\u00e1t a fi\u00f3k \u00fajb\u00f3li \u00f6sszekapcsol\u00e1s\u00e1hoz.", + "description": "K\u00e9rem, adja meg ism\u00e9t {username} jelszav\u00e1t.", "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" }, + "sms_2fa": { + "data": { + "code": "K\u00f3d" + }, + "description": "\u00cdrja be a k\u00e9tfaktoros hiteles\u00edt\u00e9si k\u00f3dot, amelyet SMS-ben k\u00fcldtek \u00d6nnek." + }, "user": { "data": { "auth_code": "Enged\u00e9lyez\u00e9si k\u00f3d", "code": "K\u00f3d (a Home Assistant felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9n haszn\u00e1latos)", "password": "Jelsz\u00f3", - "username": "E-mail" + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "A SimpliSafe rendszere webalkalmaz\u00e1son kereszt\u00fcl hiteles\u00edt\u00e9si mag\u00e1t. A technikai korl\u00e1toz\u00e1sok miatt a folyamat v\u00e9g\u00e9n van egy k\u00e9zi l\u00e9p\u00e9s; k\u00e9rj\u00fck, indul\u00e1s el\u0151tt olvassa el a [dokument\u00e1ci\u00f3t]({docs_url}).\n\n1. Ha k\u00e9szen \u00e1ll, kattintson [ide]({url}) a SimpliSafe webalkalmaz\u00e1s megnyit\u00e1s\u00e1hoz \u00e9s a hiteles\u00edt\u0151 adatok bevitel\u00e9hez. \n\n2. Amikor a folyamat befejez\u0151d\u00f6tt, t\u00e9rjen vissza ide, \u00e9s \u00edrja be a k\u00f3dot.", + "description": "Adja meg felhaszn\u00e1l\u00f3nev\u00e9t \u00e9s jelszav\u00e1t.", "title": "T\u00f6ltse ki az adatait" } } diff --git a/homeassistant/components/simplisafe/translations/id.json b/homeassistant/components/simplisafe/translations/id.json index 743af691714..ad0ad01be63 100644 --- a/homeassistant/components/simplisafe/translations/id.json +++ b/homeassistant/components/simplisafe/translations/id.json @@ -6,38 +6,37 @@ "wrong_account": "Kredensial pengguna yang diberikan tidak cocok dengan akun SimpliSafe ini." }, "error": { + "2fa_timed_out": "Tenggang waktu habis saat menunggu autentikasi dua faktor", "identifier_exists": "Akun sudah terdaftar", "invalid_auth": "Autentikasi tidak valid", "still_awaiting_mfa": "Masih menunggu pengeklikan dari email MFA", "unknown": "Kesalahan yang tidak diharapkan" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Kode Otorisasi" - }, - "description": "Masukkan kode otorisasi dari URL aplikasi web SimpliSafe:", - "title": "Selesaikan Otorisasi" - }, "mfa": { - "description": "Periksa email Anda untuk mendapatkan tautan dari SimpliSafe. Setelah memverifikasi tautan, kembali ke sini untuk menyelesaikan instalasi integrasi.", "title": "Autentikasi Multi-Faktor SimpliSafe" }, "reauth_confirm": { "data": { "password": "Kata Sandi" }, - "description": "Akses Anda telah kedaluwarsa atau dicabut. Masukkan kata sandi Anda untuk menautkan kembali akun Anda.", + "description": "Masukkan kembali kata sandi untuk {username}.", "title": "Autentikasi Ulang Integrasi" }, + "sms_2fa": { + "data": { + "code": "Kode" + }, + "description": "Masukkan kode autentikasi dua faktor yang dikirimkan kepada Anda melalui SMS." + }, "user": { "data": { "auth_code": "Kode Otorisasi", "code": "Kode (digunakan di antarmuka Home Assistant)", "password": "Kata Sandi", - "username": "Email" + "username": "Nama Pengguna" }, - "description": "SimpliSafe mengautentikasi dengan Home Assistant melalui aplikasi web. Karena keterbatasan teknis, ada langkah manual di akhir proses ini; pastikan Anda membaca [dokumentasi] ({docs_url}) sebelum memulai.\n\n1. Klik [di sini]({url}) untuk membuka aplikasi web SimpliSafe dan masukkan kredensial Anda. \n\n2. Setelah proses masuk selesai, kembali ke sini dan klik masukkan kode otorisasi di bawah ini.", + "description": "Masukkan nama pengguna dan kata sandi Anda.", "title": "Isi informasi Anda." } } diff --git a/homeassistant/components/simplisafe/translations/it.json b/homeassistant/components/simplisafe/translations/it.json index 0221536698c..ad9e491ab9a 100644 --- a/homeassistant/components/simplisafe/translations/it.json +++ b/homeassistant/components/simplisafe/translations/it.json @@ -6,38 +6,37 @@ "wrong_account": "Le credenziali utente fornite non corrispondono a questo account SimpliSafe." }, "error": { + "2fa_timed_out": "Timeout durante l'attesa dell'autenticazione a due fattori", "identifier_exists": "Account gi\u00e0 registrato", "invalid_auth": "Autenticazione non valida", "still_awaiting_mfa": "Ancora in attesa del clic sull'email MFA", "unknown": "Errore imprevisto" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Codice di autorizzazione" - }, - "description": "Immettere il codice di autorizzazione dall'URL dell'app web SimpliSafe:", - "title": "Completa l'autorizzazione" - }, "mfa": { - "description": "Controlla la tua email per trovare un collegamento da SimpliSafe. Dopo aver verificato il collegamento, torna qui per completare l'installazione dell'integrazione.", "title": "Autenticazione a pi\u00f9 fattori (MFA) SimpliSafe " }, "reauth_confirm": { "data": { "password": "Password" }, - "description": "Il tuo accesso \u00e8 scaduto o revocato. Inserisci la password per ricollegare il tuo account.", + "description": "Digita nuovamente la password per {username}.", "title": "Autentica nuovamente l'integrazione" }, + "sms_2fa": { + "data": { + "code": "Codice" + }, + "description": "Digita il codice di autenticazione a due fattori che ti \u00e8 stato inviato tramite SMS." + }, "user": { "data": { "auth_code": "Codice di autorizzazione", "code": "Codice (utilizzato nell'Interfaccia Utente di Home Assistant)", "password": "Password", - "username": "Email" + "username": "Nome utente" }, - "description": "SimpliSafe si autentica con Home Assistant tramite l'app Web SimpliSafe. A causa di limitazioni tecniche, alla fine di questo processo \u00e8 previsto un passaggio manuale; assicurati di leggere la [documentazione]({docs_url}) prima di iniziare. \n\n 1. Fai clic su [qui]({url}) per aprire l'app Web SimpliSafe e inserire le tue credenziali. \n\n 2. Quando il processo di accesso \u00e8 completo, torna qui e inserisci il codice di autorizzazione di seguito.", + "description": "Digita il tuo nome utente e password.", "title": "Inserisci le tue informazioni." } } diff --git a/homeassistant/components/simplisafe/translations/ja.json b/homeassistant/components/simplisafe/translations/ja.json index 925ae801a0d..74b0b20c65e 100644 --- a/homeassistant/components/simplisafe/translations/ja.json +++ b/homeassistant/components/simplisafe/translations/ja.json @@ -12,15 +12,7 @@ "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "\u8a8d\u8a3c\u30b3\u30fc\u30c9" - }, - "description": "SimpliSafe Web\u30a2\u30d7\u30ea\u306eURL\u304b\u3089\u8a8d\u8a3c\u30b3\u30fc\u30c9\u3092\u5165\u529b:", - "title": "\u627f\u8a8d\u7d42\u4e86" - }, "mfa": { - "description": "SimpliSafe\u304b\u3089\u306e\u30ea\u30f3\u30af\u306b\u3064\u3044\u3066\u306f\u30e1\u30fc\u30eb\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u30ea\u30f3\u30af\u3092\u78ba\u8a8d\u3057\u305f\u3089\u3001\u3053\u3053\u306b\u623b\u3063\u3066\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u5b8c\u4e86\u3057\u307e\u3059\u3002", "title": "SimpliSafe\u591a\u8981\u7d20\u8a8d\u8a3c" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/ko.json b/homeassistant/components/simplisafe/translations/ko.json index 194fa6cecf4..5c98e8abaa6 100644 --- a/homeassistant/components/simplisafe/translations/ko.json +++ b/homeassistant/components/simplisafe/translations/ko.json @@ -12,7 +12,6 @@ }, "step": { "mfa": { - "description": "\uc774\uba54\uc77c\uc5d0\uc11c SimpliSafe\uc758 \ub9c1\ud06c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \ub9c1\ud06c\ub97c \ud655\uc778\ud55c \ud6c4 \uc5ec\uae30\ub85c \ub3cc\uc544\uc640 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc124\uce58\ub97c \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "SimpliSafe \ub2e4\ub2e8\uacc4 \uc778\uc99d" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/lb.json b/homeassistant/components/simplisafe/translations/lb.json index 225c4457a10..080d8afd394 100644 --- a/homeassistant/components/simplisafe/translations/lb.json +++ b/homeassistant/components/simplisafe/translations/lb.json @@ -12,7 +12,6 @@ }, "step": { "mfa": { - "description": "Kuck den E-Mailen fir ee Link vun SimpliSafe. Nodeem de Link opgeruff gouf, komm heihinner zer\u00e9ck fir d'Installatioun vun der Integratioun ofzeschl\u00e9issen.", "title": "SimpliSafe Multi-Faktor Authentifikatioun" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index 3380746db25..33bc10ac85d 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -6,38 +6,37 @@ "wrong_account": "De opgegeven gebruikersgegevens komen niet overeen met deze SimpliSafe-account." }, "error": { + "2fa_timed_out": "Timed out tijdens het wachten op twee-factor authenticatie", "identifier_exists": "Account bestaat al", "invalid_auth": "Ongeldige authenticatie", "still_awaiting_mfa": "Wacht nog steeds op MFA-e-mailklik", "unknown": "Onverwachte fout" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Autorisatie Code" - }, - "description": "Voer de autorisatiecode in van de SimpliSafe web app URL:", - "title": "Autorisatie voltooien" - }, "mfa": { - "description": "Controleer uw e-mail voor een link van SimpliSafe. Nadat u de link hebt geverifieerd, kom hier terug om de installatie van de integratie te voltooien.", "title": "SimpliSafe Multi-Factor Authenticatie" }, "reauth_confirm": { "data": { "password": "Wachtwoord" }, - "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen.", + "description": "Voer het wachtwoord voor {username} opnieuw in.", "title": "Verifieer de integratie opnieuw" }, + "sms_2fa": { + "data": { + "code": "Code" + }, + "description": "Voer de twee-factor authenticatiecode in die u heeft ontvangen via SMS" + }, "user": { "data": { "auth_code": "Autorisatie Code", "code": "Code (gebruikt in Home Assistant)", "password": "Wachtwoord", - "username": "E-mail" + "username": "Gebruikersnaam" }, - "description": "SimpliSafe verifieert met Home Assistant via de SimpliSafe web app. Vanwege technische beperkingen is er een handmatige stap aan het einde van dit proces; zorg ervoor dat u de [documentatie]({docs_url}) leest voordat u begint.\n\n1. Klik op [hier]({url}) om de SimpliSafe web app te openen en voer uw referenties in.\n\n2. Wanneer het aanmeldingsproces is voltooid, gaat u hier terug en voert u de onderstaande autorisatiecode in.", + "description": "Voer uw gebruikersnaam en wachtwoord in.", "title": "Vul uw gegevens in" } } diff --git a/homeassistant/components/simplisafe/translations/no.json b/homeassistant/components/simplisafe/translations/no.json index 2ac57586f0b..6527f2e4ae2 100644 --- a/homeassistant/components/simplisafe/translations/no.json +++ b/homeassistant/components/simplisafe/translations/no.json @@ -6,38 +6,37 @@ "wrong_account": "Brukerlegitimasjonen som er oppgitt, samsvarer ikke med denne SimpliSafe -kontoen." }, "error": { + "2fa_timed_out": "Tidsavbrutt mens du ventet p\u00e5 tofaktorautentisering", "identifier_exists": "Konto er allerede registrert", "invalid_auth": "Ugyldig godkjenning", "still_awaiting_mfa": "Forventer fortsatt MFA-e-postklikk", "unknown": "Uventet feil" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Autorisasjonskode" - }, - "description": "Skriv inn autorisasjonskoden fra SimpliSafe -webappens URL:", - "title": "Fullf\u00f8r autorisasjon" - }, "mfa": { - "description": "Sjekk e-posten din for en lenke fra SimpliSafe. Etter \u00e5 ha bekreftet lenken, g\u00e5 tilbake hit for \u00e5 fullf\u00f8re installasjonen av integrasjonen.", "title": "SimpliSafe flertrinnsbekreftelse" }, "reauth_confirm": { "data": { "password": "Passord" }, - "description": "Tilgangen din har utl\u00f8pt eller blitt tilbakekalt. Skriv inn passordet ditt for \u00e5 koble kontoen din til p\u00e5 nytt.", + "description": "Vennligst skriv inn passordet for {username} p\u00e5 nytt.", "title": "Godkjenne integrering p\u00e5 nytt" }, + "sms_2fa": { + "data": { + "code": "Kode" + }, + "description": "Skriv inn tofaktorautentiseringskoden sendt til deg via SMS." + }, "user": { "data": { "auth_code": "Autorisasjonskode", "code": "Kode (brukt i Home Assistant brukergrensesnittet)", "password": "Passord", - "username": "E-post" + "username": "Brukernavn" }, - "description": "SimpliSafe autentiserer med Home Assistant via SimpliSafe-nettappen. P\u00e5 grunn av tekniske begrensninger er det et manuelt trinn p\u00e5 slutten av denne prosessen; s\u00f8rg for at du leser [dokumentasjonen]( {docs_url} ) f\u00f8r du starter. \n\n 1. Klikk [her]( {url} ) for \u00e5 \u00e5pne SimpliSafe-nettappen og angi legitimasjonen din. \n\n 2. N\u00e5r p\u00e5loggingsprosessen er fullf\u00f8rt, g\u00e5 tilbake hit og skriv inn autorisasjonskoden nedenfor.", + "description": "Skriv inn brukernavn og passord.", "title": "Fyll ut informasjonen din." } } diff --git a/homeassistant/components/simplisafe/translations/pl.json b/homeassistant/components/simplisafe/translations/pl.json index e95f99c1d2b..21f38d5bff5 100644 --- a/homeassistant/components/simplisafe/translations/pl.json +++ b/homeassistant/components/simplisafe/translations/pl.json @@ -6,38 +6,37 @@ "wrong_account": "Podane dane uwierzytelniaj\u0105ce u\u017cytkownika nie pasuj\u0105 do tego konta SimpliSafe." }, "error": { + "2fa_timed_out": "Przekroczono limit czasu oczekiwania na uwierzytelnianie dwusk\u0142adnikowe", "identifier_exists": "Konto jest ju\u017c zarejestrowane", "invalid_auth": "Niepoprawne uwierzytelnienie", "still_awaiting_mfa": "Wci\u0105\u017c nie potwierdzono linka w e-mailu od SimpliSafe", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Kod autoryzacji" - }, - "description": "Wprowad\u017a kod autoryzacyjny z adresu URL aplikacji internetowej SimpliSafe:", - "title": "Zako\u0144cz autoryzacj\u0119" - }, "mfa": { - "description": "Sprawd\u017a e-mail od SimpliSafe. Po zweryfikowaniu linka, wr\u00f3\u0107 tutaj, aby doko\u0144czy\u0107 instalacj\u0119 integracji.", "title": "Uwierzytelnianie wielosk\u0142adnikowe SimpliSafe" }, "reauth_confirm": { "data": { "password": "Has\u0142o" }, - "description": "Tw\u00f3j dost\u0119p wygas\u0142 lub zosta\u0142 uniewa\u017cniony. Wprowad\u017a has\u0142o, aby ponownie po\u0142\u0105czy\u0107 swoje konto.", + "description": "Wprowad\u017a ponownie has\u0142o dla u\u017cytkownika {username}.", "title": "Ponownie uwierzytelnij integracj\u0119" }, + "sms_2fa": { + "data": { + "code": "Kod" + }, + "description": "Wprowad\u017a kod uwierzytelniania dwusk\u0142adnikowego, kt\u00f3ry zosta\u0142 wys\u0142any do Ciebie SMS-em." + }, "user": { "data": { "auth_code": "Kod autoryzacji", "code": "Kod (u\u017cywany w interfejsie Home Assistant)", "password": "Has\u0142o", - "username": "Adres e-mail" + "username": "Nazwa u\u017cytkownika" }, - "description": "SimpliSafe uwierzytelnia si\u0119 z Home Assistantem za po\u015brednictwem aplikacji internetowej SimpliSafe. Ze wzgl\u0119du na ograniczenia techniczne na ko\u0144cu tego procesu znajduje si\u0119 r\u0119czny krok; upewnij si\u0119, \u017ce przed rozpocz\u0119ciem przeczyta\u0142e\u015b [dokumentacj\u0119]( {docs_url} ).\n\n1. Kliknij [tutaj]( {url} ), aby otworzy\u0107 aplikacj\u0119 internetow\u0105 SimpliSafe i wprowadzi\u0107 swoje dane uwierzytelniaj\u0105ce. \n\n2. Po zako\u0144czeniu procesu logowania wr\u00f3\u0107 tutaj i wprowad\u017a poni\u017cszy kod autoryzacyjny.", + "description": "Wprowad\u017a swoj\u0105 nazw\u0119 u\u017cytkownika i has\u0142o.", "title": "Wprowad\u017a dane" } } diff --git a/homeassistant/components/simplisafe/translations/pt-BR.json b/homeassistant/components/simplisafe/translations/pt-BR.json index d1473074cca..1dec82ec74d 100644 --- a/homeassistant/components/simplisafe/translations/pt-BR.json +++ b/homeassistant/components/simplisafe/translations/pt-BR.json @@ -6,38 +6,37 @@ "wrong_account": "As credenciais de usu\u00e1rio fornecidas n\u00e3o correspondem a esta conta SimpliSafe." }, "error": { + "2fa_timed_out": "Expirou enquanto aguardava a autentica\u00e7\u00e3o de dois fatores", "identifier_exists": "Conta j\u00e1 cadastrada", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "still_awaiting_mfa": "Ainda aguardando clique no e-mail da MFA", "unknown": "Erro inesperado" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "C\u00f3digo de Autoriza\u00e7\u00e3o" - }, - "description": "Insira o c\u00f3digo de autoriza\u00e7\u00e3o do URL do aplicativo Web SimpliSafe:", - "title": "Concluir autoriza\u00e7\u00e3o" - }, "mfa": { - "description": "Verifique seu e-mail para obter um link do SimpliSafe. Ap\u00f3s verificar o link, volte aqui para concluir a instala\u00e7\u00e3o da integra\u00e7\u00e3o.", "title": "Autentica\u00e7\u00e3o SimpliSafe multifator" }, "reauth_confirm": { "data": { "password": "Senha" }, - "description": "Seu acesso expirou ou foi revogado. Digite sua senha para vincular novamente sua conta.", + "description": "Por favor, digite novamente a senha para {username} .", "title": "Reautenticar Integra\u00e7\u00e3o" }, + "sms_2fa": { + "data": { + "code": "C\u00f3digo" + }, + "description": "Insira o c\u00f3digo de autentica\u00e7\u00e3o de dois fatores enviado a voc\u00ea via SMS." + }, "user": { "data": { "auth_code": "C\u00f3digo de autoriza\u00e7\u00e3o", "code": "C\u00f3digo (usado na IU do Home Assistant)", "password": "Senha", - "username": "Email" + "username": "Usu\u00e1rio" }, - "description": "O SimpliSafe autentica com o Home Assistant por meio do aplicativo da Web SimpliSafe. Por limita\u00e7\u00f5es t\u00e9cnicas, existe uma etapa manual ao final deste processo; certifique-se de ler a [documenta\u00e7\u00e3o]( {docs_url} ) antes de come\u00e7ar. \n\n 1. Clique [aqui]( {url} ) para abrir o aplicativo da web SimpliSafe e insira suas credenciais. \n\n 2. Quando o processo de login estiver conclu\u00eddo, retorne aqui e insira o c\u00f3digo de autoriza\u00e7\u00e3o abaixo.", + "description": "Insira seu nome de usu\u00e1rio e senha.", "title": "Preencha suas informa\u00e7\u00f5es." } } diff --git a/homeassistant/components/simplisafe/translations/ru.json b/homeassistant/components/simplisafe/translations/ru.json index 306a0180d88..f2907bd6c47 100644 --- a/homeassistant/components/simplisafe/translations/ru.json +++ b/homeassistant/components/simplisafe/translations/ru.json @@ -12,15 +12,7 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { - "input_auth_code": { - "data": { - "auth_code": "\u041a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438" - }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0437 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f SimpliSafe:", - "title": "\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438" - }, "mfa": { - "description": "\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0441\u0432\u043e\u044e \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443\u044e \u043f\u043e\u0447\u0442\u0443 \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043e\u0442 SimpliSafe. \u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043e\u0442\u043a\u0440\u043e\u0435\u0442\u0435 \u0441\u0441\u044b\u043b\u043a\u0443, \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438.", "title": "\u0414\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f SimpliSafe" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/sl.json b/homeassistant/components/simplisafe/translations/sl.json index bbbe8034d06..6f326bc5b68 100644 --- a/homeassistant/components/simplisafe/translations/sl.json +++ b/homeassistant/components/simplisafe/translations/sl.json @@ -8,13 +8,6 @@ "identifier_exists": "Ra\u010dun je \u017ee registriran" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Avtorizacijska koda" - }, - "description": "Vnesite avtorizacijsko kodo iz URL-ja spletne aplikacije SimpliSafe:", - "title": "Dokon\u010daj avtorizacijo" - }, "user": { "data": { "code": "Koda (uporablja se v uporabni\u0161kem vmesniku Home Assistant)", diff --git a/homeassistant/components/simplisafe/translations/tr.json b/homeassistant/components/simplisafe/translations/tr.json index 721502ca0b1..255207833c7 100644 --- a/homeassistant/components/simplisafe/translations/tr.json +++ b/homeassistant/components/simplisafe/translations/tr.json @@ -12,15 +12,7 @@ "unknown": "Beklenmeyen hata" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "Yetkilendirme Kodu" - }, - "description": "SimpliSafe web uygulamas\u0131 URL'sinden yetkilendirme kodunu girin:", - "title": "Yetkilendirmeyi Bitir" - }, "mfa": { - "description": "SimpliSafe'den bir ba\u011flant\u0131 i\u00e7in e-postan\u0131z\u0131 kontrol edin. Ba\u011flant\u0131y\u0131 do\u011frulad\u0131ktan sonra, entegrasyonun kurulumunu tamamlamak i\u00e7in buraya geri d\u00f6n\u00fcn.", "title": "SimpliSafe \u00c7ok Fakt\u00f6rl\u00fc Kimlik Do\u011frulama" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/uk.json b/homeassistant/components/simplisafe/translations/uk.json index 0a51f129e5f..8ca29743c5b 100644 --- a/homeassistant/components/simplisafe/translations/uk.json +++ b/homeassistant/components/simplisafe/translations/uk.json @@ -12,7 +12,6 @@ }, "step": { "mfa": { - "description": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0441\u0432\u043e\u044e \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443 \u043f\u043e\u0448\u0442\u0443 \u043d\u0430 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0432\u0456\u0434 SimpliSafe. \u041f\u0456\u0441\u043b\u044f \u0442\u043e\u0433\u043e \u044f\u043a \u0432\u0456\u0434\u043a\u0440\u0438\u0454\u0442\u0435 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f, \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438, \u0449\u043e\u0431 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.", "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f SimpliSafe" }, "reauth_confirm": { diff --git a/homeassistant/components/simplisafe/translations/zh-Hant.json b/homeassistant/components/simplisafe/translations/zh-Hant.json index ae22f8c8f0d..1b10153ab05 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hant.json +++ b/homeassistant/components/simplisafe/translations/zh-Hant.json @@ -6,38 +6,37 @@ "wrong_account": "\u6240\u4ee5\u63d0\u4f9b\u7684\u6191\u8b49\u8207 Simplisafe \u5e33\u865f\u4e0d\u7b26\u3002" }, "error": { + "2fa_timed_out": "\u7b49\u5f85\u5169\u6b65\u9a5f\u9a57\u8b49\u78bc\u903e\u6642", "identifier_exists": "\u5e33\u865f\u5df2\u8a3b\u518a", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "still_awaiting_mfa": "\u4ecd\u5728\u7b49\u5019\u9ede\u64ca\u591a\u6b65\u9a5f\u8a8d\u8b49\u90f5\u4ef6", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { - "input_auth_code": { - "data": { - "auth_code": "\u8a8d\u8b49\u78bc" - }, - "description": "\u8f38\u5165\u7531 SimpliSafe \u7db2\u9801 App URL \u6240\u53d6\u5f97\u4e4b\u8a8d\u8b49\u78bc\uff1a", - "title": "\u5b8c\u6210\u8a8d\u8b49" - }, "mfa": { - "description": "\u8acb\u6aa2\u67e5\u4f86\u81ea SimpliSafe \u7684\u90f5\u4ef6\u4ee5\u53d6\u5f97\u9023\u7d50\u3002\u78ba\u8a8d\u9023\u7d50\u5f8c\uff0c\u518d\u56de\u5230\u6b64\u8655\u4ee5\u5b8c\u6210\u6574\u5408\u5b89\u88dd\u3002", "title": "SimpliSafe \u591a\u6b65\u9a5f\u9a57\u8b49" }, "reauth_confirm": { "data": { "password": "\u5bc6\u78bc" }, - "description": "\u5b58\u53d6\u6b0a\u9650\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", + "description": "\u8acb\u8f38\u5165\u5e33\u865f {username} \u5bc6\u78bc\u3002", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, + "sms_2fa": { + "data": { + "code": "\u9a57\u8b49\u78bc" + }, + "description": "\u8f38\u5165\u7c21\u8a0a\u6240\u6536\u5230\u7684\u5169\u6b65\u9a5f\u9a57\u8b49\u78bc\u3002" + }, "user": { "data": { "auth_code": "\u8a8d\u8b49\u78bc", "code": "\u9a57\u8b49\u78bc\uff08\u4f7f\u7528\u65bc Home Assistant UI\uff09", "password": "\u5bc6\u78bc", - "username": "\u96fb\u5b50\u90f5\u4ef6" + "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "SimpliSafe \u70ba\u900f\u904e Web App \u65b9\u5f0f\u7684\u8a8d\u8b49\u6a5f\u5236\u3002\u7531\u65bc\u6280\u8853\u9650\u5236\u3001\u65bc\u6b64\u904e\u7a0b\u7d50\u675f\u6642\u5c07\u6703\u6709\u4e00\u6b65\u624b\u52d5\u968e\u6bb5\uff1b\u65bc\u958b\u59cb\u524d\u3001\u8acb\u78ba\u5b9a\u53c3\u95b1 [\u76f8\u95dc\u6587\u4ef6]({docs_url})\u3002\n\n1. \u9ede\u9078 [\u6b64\u8655] ({url}) \u4ee5\u958b\u555f SimpliSafe Web App \u4e26\u8f38\u5165\u9a57\u8b49\u3002\n\n2. \u7576\u767b\u5165\u5b8c\u6210\u5f8c\u3001\u56de\u5230\u6b64\u8655\u65bc\u4e0b\u65b9\u8f38\u5165\u8a8d\u8b49\u78bc\u3002", + "description": "\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31\u8207\u5bc6\u78bc\u3002", "title": "\u586b\u5beb\u8cc7\u8a0a\u3002" } } diff --git a/homeassistant/components/siren/__init__.py b/homeassistant/components/siren/__init__.py index ed0e8b8645f..42dfd0ed3df 100644 --- a/homeassistant/components/siren/__init__.py +++ b/homeassistant/components/siren/__init__.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType -from .const import ( +from .const import ( # noqa: F401 ATTR_AVAILABLE_TONES, ATTR_DURATION, ATTR_TONE, @@ -31,6 +31,7 @@ from .const import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET, + SirenEntityFeature, ) _LOGGER = logging.getLogger(__name__) @@ -62,7 +63,7 @@ def process_turn_on_params( """ supported_features = siren.supported_features or 0 - if not supported_features & SUPPORT_TONES: + if not supported_features & SirenEntityFeature.TONES: params.pop(ATTR_TONE, None) elif (tone := params.get(ATTR_TONE)) is not None: # Raise an exception if the specified tone isn't available @@ -88,9 +89,9 @@ def process_turn_on_params( key for key, value in siren.available_tones.items() if value == tone ) - if not supported_features & SUPPORT_DURATION: + if not supported_features & SirenEntityFeature.DURATION: params.pop(ATTR_DURATION, None) - if not supported_features & SUPPORT_VOLUME_SET: + if not supported_features & SirenEntityFeature.VOLUME_SET: params.pop(ATTR_VOLUME_LEVEL, None) return params @@ -117,13 +118,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) component.async_register_entity_service( - SERVICE_TURN_ON, TURN_ON_SCHEMA, async_handle_turn_on_service, [SUPPORT_TURN_ON] + SERVICE_TURN_ON, + TURN_ON_SCHEMA, + async_handle_turn_on_service, + [SirenEntityFeature.TURN_ON], ) component.async_register_entity_service( - SERVICE_TURN_OFF, {}, "async_turn_off", [SUPPORT_TURN_OFF] + SERVICE_TURN_OFF, {}, "async_turn_off", [SirenEntityFeature.TURN_OFF] ) component.async_register_entity_service( - SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_TURN_ON & SUPPORT_TURN_OFF] + SERVICE_TOGGLE, + {}, + "async_toggle", + [SirenEntityFeature.TURN_ON & SirenEntityFeature.TURN_OFF], ) return True @@ -145,12 +152,14 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class SirenEntityDescription(ToggleEntityDescription): """A class that describes siren entities.""" + available_tones: list[int | str] | dict[int, str] | None = None + class SirenEntity(ToggleEntity): """Representation of a siren device.""" entity_description: SirenEntityDescription - _attr_available_tones: list[int | str] | dict[int, str] | None = None + _attr_available_tones: list[int | str] | dict[int, str] | None @final @property @@ -158,7 +167,10 @@ class SirenEntity(ToggleEntity): """Return capability attributes.""" supported_features = self.supported_features or 0 - if supported_features & SUPPORT_TONES and self.available_tones is not None: + if ( + supported_features & SirenEntityFeature.TONES + and self.available_tones is not None + ): return {ATTR_AVAILABLE_TONES: self.available_tones} return None @@ -168,6 +180,10 @@ class SirenEntity(ToggleEntity): """ Return a list of available tones. - Requires SUPPORT_TONES. + Requires SirenEntityFeature.TONES. """ - return self._attr_available_tones + if hasattr(self, "_attr_available_tones"): + return self._attr_available_tones + if hasattr(self, "entity_description"): + return self.entity_description.available_tones + return None diff --git a/homeassistant/components/siren/const.py b/homeassistant/components/siren/const.py index 2faab9ed8e8..6ef7c9e6955 100644 --- a/homeassistant/components/siren/const.py +++ b/homeassistant/components/siren/const.py @@ -1,5 +1,6 @@ """Constants for the siren component.""" +from enum import IntEnum from typing import Final DOMAIN: Final = "siren" @@ -10,6 +11,19 @@ ATTR_AVAILABLE_TONES: Final = "available_tones" ATTR_DURATION: Final = "duration" ATTR_VOLUME_LEVEL: Final = "volume_level" + +class SirenEntityFeature(IntEnum): + """Supported features of the siren entity.""" + + TURN_ON = 1 + TURN_OFF = 2 + TONES = 4 + VOLUME_SET = 8 + DURATION = 16 + + +# These constants are deprecated as of Home Assistant 2022.5 +# Please use the SirenEntityFeature enum instead. SUPPORT_TURN_ON: Final = 1 SUPPORT_TURN_OFF: Final = 2 SUPPORT_TONES: Final = 4 diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py index be905c0c8b1..7601ccf7657 100644 --- a/homeassistant/components/sisyphus/light.py +++ b/homeassistant/components/sisyphus/light.py @@ -5,7 +5,7 @@ import logging import aiohttp -from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady @@ -16,8 +16,6 @@ from . import DATA_SISYPHUS _LOGGER = logging.getLogger(__name__) -SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS - async def async_setup_platform( hass: HomeAssistant, @@ -41,6 +39,9 @@ async def async_setup_platform( class SisyphusLight(LightEntity): """Representation of a Sisyphus table as a light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, name, table): """Initialize the Sisyphus table.""" self._name = name @@ -79,11 +80,6 @@ class SisyphusLight(LightEntity): """Return the current brightness of the table's ring light.""" return self._table.brightness * 255 - @property - def supported_features(self): - """Return the features supported by the table; i.e. brightness.""" - return SUPPORTED_FEATURES - async def async_turn_off(self, **kwargs): """Put the table to sleep.""" await self._table.sleep() diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index 685a8126835..208799361fe 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -4,17 +4,9 @@ from __future__ import annotations import aiohttp from sisyphus_control import Track -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SHUFFLE_SET, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -32,18 +24,6 @@ from . import DATA_SISYPHUS MEDIA_TYPE_TRACK = "sisyphus_track" -SUPPORTED_FEATURES = ( - SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_TURN_OFF - | SUPPORT_TURN_ON - | SUPPORT_PAUSE - | SUPPORT_SHUFFLE_SET - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY -) - async def async_setup_platform( hass: HomeAssistant, @@ -67,6 +47,18 @@ async def async_setup_platform( class SisyphusPlayer(MediaPlayerEntity): """Representation of a Sisyphus table as a media player device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY + ) + def __init__(self, name, host, table): """Initialize the Sisyphus media device.""" self._name = name @@ -163,11 +155,6 @@ class SisyphusPlayer(MediaPlayerEntity): """Return the last time we got a position update.""" return self._table.active_track_remaining_time_as_of - @property - def supported_features(self): - """Return the features supported by this table.""" - return SUPPORTED_FEATURES - @property def media_image_url(self): """Return the URL for a thumbnail image of the current track.""" diff --git a/homeassistant/components/skybell/light.py b/homeassistant/components/skybell/light.py index 6285c183384..7fbd1519e26 100644 --- a/homeassistant/components/skybell/light.py +++ b/homeassistant/components/skybell/light.py @@ -4,8 +4,7 @@ from __future__ import annotations from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.core import HomeAssistant @@ -45,15 +44,13 @@ def _to_hass_level(level): class SkybellLight(SkybellDevice, LightEntity): """A binary sensor implementation for Skybell devices.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, device): """Initialize a light for a Skybell device.""" super().__init__(device) - self._name = self._device.name - - @property - def name(self): - """Return the name of the sensor.""" - return self._name + self._attr_name = device.name def turn_on(self, **kwargs): """Turn on the light.""" @@ -83,8 +80,3 @@ class SkybellLight(SkybellDevice, LightEntity): def hs_color(self): """Return the color of the light.""" return color_util.color_RGB_to_hs(*self._device.led_rgb) - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR diff --git a/homeassistant/components/sleepiq/light.py b/homeassistant/components/sleepiq/light.py index 1017051f94c..e0b98a37362 100644 --- a/homeassistant/components/sleepiq/light.py +++ b/homeassistant/components/sleepiq/light.py @@ -4,7 +4,7 @@ from typing import Any from asyncsleepiq import SleepIQBed, SleepIQLight -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -34,6 +34,9 @@ async def async_setup_entry( class SleepIQLightEntity(SleepIQBedEntity, LightEntity): """Representation of a light.""" + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + def __init__( self, coordinator: DataUpdateCoordinator, bed: SleepIQBed, light: SleepIQLight ) -> None: diff --git a/homeassistant/components/sleepiq/translations/fr.json b/homeassistant/components/sleepiq/translations/fr.json index 9c43b00feba..f40576959bb 100644 --- a/homeassistant/components/sleepiq/translations/fr.json +++ b/homeassistant/components/sleepiq/translations/fr.json @@ -13,6 +13,7 @@ "data": { "password": "Mot de passe" }, + "description": "L'int\u00e9gration SleepIQ doit r\u00e9-authentifier votre compte {username}.", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { diff --git a/homeassistant/components/slimproto/__init__.py b/homeassistant/components/slimproto/__init__.py new file mode 100644 index 00000000000..96932e1e81f --- /dev/null +++ b/homeassistant/components/slimproto/__init__.py @@ -0,0 +1,50 @@ +"""SlimProto Player integration.""" +from __future__ import annotations + +from aioslimproto import SlimServer + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Event, HomeAssistant +from homeassistant.helpers import device_registry as dr + +from .const import DOMAIN + +PLATFORMS = ["media_player"] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up from a config entry.""" + slimserver = SlimServer() + await slimserver.start() + + hass.data[DOMAIN] = slimserver + + # initialize platform(s) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + # setup event listeners + async def on_hass_stop(event: Event) -> None: + """Handle incoming stop event from Home Assistant.""" + await slimserver.stop() + + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) + ) + + return True + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove a config entry from a device.""" + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_success = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_success: + await hass.data.pop(DOMAIN).stop() + return unload_success diff --git a/homeassistant/components/slimproto/config_flow.py b/homeassistant/components/slimproto/config_flow.py new file mode 100644 index 00000000000..7e2e96f74dc --- /dev/null +++ b/homeassistant/components/slimproto/config_flow.py @@ -0,0 +1,26 @@ +"""Config flow for SlimProto Player integration.""" + +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigFlow +from homeassistant.data_entry_flow import FlowResult + +from .const import DEFAULT_NAME, DOMAIN + + +class SlimProtoConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for SlimProto Player.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + # we have nothing to configure so simply create the entry + return self.async_create_entry(title=DEFAULT_NAME, data={}) diff --git a/homeassistant/components/slimproto/const.py b/homeassistant/components/slimproto/const.py new file mode 100644 index 00000000000..3b85de5d794 --- /dev/null +++ b/homeassistant/components/slimproto/const.py @@ -0,0 +1,8 @@ +"""Constants for SlimProto Player integration.""" + + +DOMAIN = "slimproto" + +DEFAULT_NAME = "SlimProto Player" + +PLAYER_EVENT = f"{DOMAIN}_event" diff --git a/homeassistant/components/slimproto/manifest.json b/homeassistant/components/slimproto/manifest.json new file mode 100644 index 00000000000..23b3198d7e4 --- /dev/null +++ b/homeassistant/components/slimproto/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "slimproto", + "name": "SlimProto (Squeezebox players)", + "config_flow": true, + "iot_class": "local_push", + "documentation": "https://www.home-assistant.io/integrations/slimproto", + "requirements": ["aioslimproto==2.0.1"], + "codeowners": ["@marcelveldt"], + "after_dependencies": ["media_source"] +} diff --git a/homeassistant/components/slimproto/media_player.py b/homeassistant/components/slimproto/media_player.py new file mode 100644 index 00000000000..6b1989830e2 --- /dev/null +++ b/homeassistant/components/slimproto/media_player.py @@ -0,0 +1,220 @@ +"""MediaPlayer platform for SlimProto Player integration.""" +from __future__ import annotations + +import asyncio + +from aioslimproto.client import PlayerState, SlimClient +from aioslimproto.const import EventType, SlimEvent +from aioslimproto.server import SlimServer + +from homeassistant.components import media_source +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerDeviceClass, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) +from homeassistant.components.media_player.browse_media import ( + async_process_play_media_url, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.dt import utcnow + +from .const import DEFAULT_NAME, DOMAIN, PLAYER_EVENT + +STATE_MAPPING = { + PlayerState.IDLE: STATE_IDLE, + PlayerState.PLAYING: STATE_PLAYING, + PlayerState.PAUSED: STATE_PAUSED, +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up SlimProto MediaPlayer(s) from Config Entry.""" + slimserver: SlimServer = hass.data[DOMAIN] + added_ids = set() + + async def async_add_player(player: SlimClient) -> None: + """Add MediaPlayerEntity from SlimClient.""" + # we delay adding the player a small bit because the player name may be received + # just a bit after connect. This way we can create a device reg entry with the correct name + # the name will either be available within a few milliseconds after connect or not at all + # (its an optional data packet) + for _ in range(10): + if player.player_id not in player.name: + break + await asyncio.sleep(0.1) + async_add_entities([SlimProtoPlayer(slimserver, player)]) + + async def on_slim_event(event: SlimEvent) -> None: + """Handle player added/connected event.""" + if event.player_id in added_ids: + return + added_ids.add(event.player_id) + player = slimserver.get_player(event.player_id) + await async_add_player(player) + + # register listener for new players + config_entry.async_on_unload( + slimserver.subscribe(on_slim_event, EventType.PLAYER_CONNECTED) + ) + + # add all current items in controller + await asyncio.gather(*(async_add_player(player) for player in slimserver.players)) + + +class SlimProtoPlayer(MediaPlayerEntity): + """Representation of MediaPlayerEntity from SlimProto Player.""" + + _attr_should_poll = False + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) + _attr_device_class = MediaPlayerDeviceClass.SPEAKER + + def __init__(self, slimserver: SlimServer, player: SlimClient) -> None: + """Initialize MediaPlayer entity.""" + self.slimserver = slimserver + self.player = player + self._attr_unique_id = player.player_id + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.player.player_id)}, + manufacturer=DEFAULT_NAME, + model=self.player.device_model or self.player.device_type, + name=self.player.name, + hw_version=self.player.firmware, + ) + # PiCore + SqueezeESP32 player has web interface + if "-pCP" in self.player.firmware or self.player.device_model == "SqueezeESP32": + self._attr_device_info[ + "configuration_url" + ] = f"http://{self.player.device_address}" + self.update_attributes() + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + self.update_attributes() + self.async_on_remove( + self.slimserver.subscribe( + self._on_slim_event, + ( + EventType.PLAYER_UPDATED, + EventType.PLAYER_CONNECTED, + EventType.PLAYER_DISCONNECTED, + EventType.PLAYER_NAME_RECEIVED, + EventType.PLAYER_CLI_EVENT, + ), + player_filter=self.player.player_id, + ) + ) + + @property + def available(self) -> bool: + """Return availability of entity.""" + return self.player.connected + + @property + def state(self) -> str: + """Return current state.""" + if not self.player.powered: + return STATE_OFF + return STATE_MAPPING[self.player.state] + + @callback + def update_attributes(self) -> None: + """Handle player updates.""" + self._attr_name = self.player.name + self._attr_volume_level = self.player.volume_level / 100 + self._attr_media_position = self.player.elapsed_seconds + self._attr_media_position_updated_at = utcnow() + self._attr_media_content_id = self.player.current_url + self._attr_media_content_type = "music" + + async def async_media_play(self) -> None: + """Send play command to device.""" + await self.player.play() + + async def async_media_pause(self) -> None: + """Send pause command to device.""" + await self.player.pause() + + async def async_media_stop(self) -> None: + """Send stop command to device.""" + await self.player.stop() + + async def async_set_volume_level(self, volume: float) -> None: + """Send new volume_level to device.""" + volume = round(volume * 100) + await self.player.volume_set(volume) + + async def async_mute_volume(self, mute: bool) -> None: + """Mute the volume.""" + await self.player.mute(mute) + + async def async_turn_on(self) -> None: + """Turn on device.""" + await self.player.power(True) + + async def async_turn_off(self) -> None: + """Turn off device.""" + await self.player.power(False) + + async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> None: + """Send the play_media command to the media player.""" + to_send_media_type: str | None = media_type + # Handle media_source + if media_source.is_media_source_id(media_id): + sourced_media = await media_source.async_resolve_media(self.hass, media_id) + media_id = sourced_media.url + to_send_media_type = sourced_media.mime_type + + if to_send_media_type and not to_send_media_type.startswith("audio/"): + to_send_media_type = None + media_id = async_process_play_media_url(self.hass, media_id) + + await self.player.play_url(media_id, mime_type=to_send_media_type) + + async def async_browse_media( + self, media_content_type=None, media_content_id=None + ) -> BrowseMedia: + """Implement the websocket media browsing helper.""" + return await media_source.async_browse_media( + self.hass, + media_content_id, + content_filter=lambda item: item.media_content_type.startswith("audio/"), + ) + + async def _on_slim_event(self, event: SlimEvent) -> None: + """Call when we receive an event from SlimProto.""" + if event.type == EventType.PLAYER_CONNECTED: + # player reconnected, update our player object + self.player = self.slimserver.get_player(event.player_id) + if event.type == EventType.PLAYER_CLI_EVENT: + # rpc event from player such as a button press, + # forward on the eventbus for others to handle + dev_id = self.registry_entry.device_id if self.registry_entry else None + evt_data = { + **event.data, + "entity_id": self.entity_id, + "device_id": dev_id, + } + self.hass.bus.async_fire(PLAYER_EVENT, evt_data) + return + self.update_attributes() + self.async_write_ha_state() diff --git a/homeassistant/components/slimproto/strings.json b/homeassistant/components/slimproto/strings.json new file mode 100644 index 00000000000..8eb261b58fc --- /dev/null +++ b/homeassistant/components/slimproto/strings.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": {} + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + } +} diff --git a/homeassistant/components/slimproto/translations/de.json b/homeassistant/components/slimproto/translations/de.json new file mode 100644 index 00000000000..69b89b76225 --- /dev/null +++ b/homeassistant/components/slimproto/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/el.json b/homeassistant/components/slimproto/translations/el.json new file mode 100644 index 00000000000..df583a2f8f8 --- /dev/null +++ b/homeassistant/components/slimproto/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/en.json b/homeassistant/components/slimproto/translations/en.json new file mode 100644 index 00000000000..2d579aab7c8 --- /dev/null +++ b/homeassistant/components/slimproto/translations/en.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/et.json b/homeassistant/components/slimproto/translations/et.json new file mode 100644 index 00000000000..e8b5eae4149 --- /dev/null +++ b/homeassistant/components/slimproto/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/fr.json b/homeassistant/components/slimproto/translations/fr.json new file mode 100644 index 00000000000..807ba246694 --- /dev/null +++ b/homeassistant/components/slimproto/translations/fr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/hu.json b/homeassistant/components/slimproto/translations/hu.json new file mode 100644 index 00000000000..edd3c258b81 --- /dev/null +++ b/homeassistant/components/slimproto/translations/hu.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "user": { + "one": "\u00dcres", + "other": "\u00dcres" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/id.json b/homeassistant/components/slimproto/translations/id.json new file mode 100644 index 00000000000..3a870e47986 --- /dev/null +++ b/homeassistant/components/slimproto/translations/id.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/it.json b/homeassistant/components/slimproto/translations/it.json new file mode 100644 index 00000000000..42bc1ef9f7d --- /dev/null +++ b/homeassistant/components/slimproto/translations/it.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "step": { + "user": { + "one": "Vuoto", + "other": "Vuoti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/nl.json b/homeassistant/components/slimproto/translations/nl.json new file mode 100644 index 00000000000..79aaec23123 --- /dev/null +++ b/homeassistant/components/slimproto/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/no.json b/homeassistant/components/slimproto/translations/no.json new file mode 100644 index 00000000000..aa380f0385a --- /dev/null +++ b/homeassistant/components/slimproto/translations/no.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/pl.json b/homeassistant/components/slimproto/translations/pl.json new file mode 100644 index 00000000000..96fba53c20f --- /dev/null +++ b/homeassistant/components/slimproto/translations/pl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/pt-BR.json b/homeassistant/components/slimproto/translations/pt-BR.json new file mode 100644 index 00000000000..9ab59f40649 --- /dev/null +++ b/homeassistant/components/slimproto/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/slimproto/translations/zh-Hant.json b/homeassistant/components/slimproto/translations/zh-Hant.json new file mode 100644 index 00000000000..942e17282bb --- /dev/null +++ b/homeassistant/components/slimproto/translations/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index b9f94b94dde..5ab250aaf80 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -3,7 +3,7 @@ "name": "Smappee", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/smappee", - "dependencies": ["http"], + "dependencies": ["auth"], "requirements": ["pysmappee==0.2.29"], "codeowners": ["@bsmappee"], "zeroconf": [ diff --git a/homeassistant/components/smappee/translations/hu.json b/homeassistant/components/smappee/translations/hu.json index 9c3d90ac43e..712cbd0951d 100644 --- a/homeassistant/components/smappee/translations/hu.json +++ b/homeassistant/components/smappee/translations/hu.json @@ -7,7 +7,7 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_mdns": "Nem t\u00e1mogatott eszk\u00f6z a Smappee integr\u00e1ci\u00f3hoz.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3." }, "flow_title": "{name}", "step": { @@ -24,7 +24,7 @@ "description": "Adja meg a c\u00edmet a Smappee helyi integr\u00e1ci\u00f3j\u00e1nak elind\u00edt\u00e1s\u00e1hoz" }, "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "zeroconf_confirm": { "description": "Hozz\u00e1 szeretn\u00e9 adni a \"{serialnumber} serialnumber}\" sorozatsz\u00e1m\u00fa Smappee -eszk\u00f6zt az HomeAssistanthoz?", diff --git a/homeassistant/components/smappee/translations/it.json b/homeassistant/components/smappee/translations/it.json index 3a493371b16..7c18d944ab7 100644 --- a/homeassistant/components/smappee/translations/it.json +++ b/homeassistant/components/smappee/translations/it.json @@ -6,7 +6,7 @@ "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "cannot_connect": "Impossibile connettersi", "invalid_mdns": "Dispositivo non supportato per l'integrazione Smappee.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})" }, "flow_title": "{name}", diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index edc0688b5f7..9bc287b054c 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -12,20 +12,9 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -37,47 +26,47 @@ from .const import DATA_BROKERS, DOMAIN ATTR_OPERATION_STATE = "operation_state" MODE_TO_STATE = { - "auto": HVAC_MODE_HEAT_COOL, - "cool": HVAC_MODE_COOL, - "eco": HVAC_MODE_AUTO, - "rush hour": HVAC_MODE_AUTO, - "emergency heat": HVAC_MODE_HEAT, - "heat": HVAC_MODE_HEAT, - "off": HVAC_MODE_OFF, + "auto": HVACMode.HEAT_COOL, + "cool": HVACMode.COOL, + "eco": HVACMode.AUTO, + "rush hour": HVACMode.AUTO, + "emergency heat": HVACMode.HEAT, + "heat": HVACMode.HEAT, + "off": HVACMode.OFF, } STATE_TO_MODE = { - HVAC_MODE_HEAT_COOL: "auto", - HVAC_MODE_COOL: "cool", - HVAC_MODE_HEAT: "heat", - HVAC_MODE_OFF: "off", + HVACMode.HEAT_COOL: "auto", + HVACMode.COOL: "cool", + HVACMode.HEAT: "heat", + HVACMode.OFF: "off", } OPERATING_STATE_TO_ACTION = { - "cooling": CURRENT_HVAC_COOL, - "fan only": CURRENT_HVAC_FAN, - "heating": CURRENT_HVAC_HEAT, - "idle": CURRENT_HVAC_IDLE, - "pending cool": CURRENT_HVAC_COOL, - "pending heat": CURRENT_HVAC_HEAT, - "vent economizer": CURRENT_HVAC_FAN, + "cooling": HVACAction.COOLING, + "fan only": HVACAction.FAN, + "heating": HVACAction.HEATING, + "idle": HVACAction.IDLE, + "pending cool": HVACAction.COOLING, + "pending heat": HVACAction.HEATING, + "vent economizer": HVACAction.FAN, } AC_MODE_TO_STATE = { - "auto": HVAC_MODE_HEAT_COOL, - "cool": HVAC_MODE_COOL, - "dry": HVAC_MODE_DRY, - "coolClean": HVAC_MODE_COOL, - "dryClean": HVAC_MODE_DRY, - "heat": HVAC_MODE_HEAT, - "heatClean": HVAC_MODE_HEAT, - "fanOnly": HVAC_MODE_FAN_ONLY, + "auto": HVACMode.HEAT_COOL, + "cool": HVACMode.COOL, + "dry": HVACMode.DRY, + "coolClean": HVACMode.COOL, + "dryClean": HVACMode.DRY, + "heat": HVACMode.HEAT, + "heatClean": HVACMode.HEAT, + "fanOnly": HVACMode.FAN_ONLY, } STATE_TO_AC_MODE = { - HVAC_MODE_HEAT_COOL: "auto", - HVAC_MODE_COOL: "cool", - HVAC_MODE_DRY: "dry", - HVAC_MODE_HEAT: "heat", - HVAC_MODE_FAN_ONLY: "fanOnly", + HVACMode.HEAT_COOL: "auto", + HVACMode.COOL: "cool", + HVACMode.DRY: "dry", + HVACMode.HEAT: "heat", + HVACMode.FAN_ONLY: "fanOnly", } UNIT_MAP = {"C": TEMP_CELSIUS, "F": TEMP_FAHRENHEIT} @@ -159,16 +148,19 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): def __init__(self, device): """Init the class.""" super().__init__(device) - self._supported_features = self._determine_features() + self._attr_supported_features = self._determine_features() self._hvac_mode = None self._hvac_modes = None def _determine_features(self): - flags = SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE + flags = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) if self._device.get_capability( Capability.thermostat_fan_mode, Capability.thermostat ): - flags |= SUPPORT_FAN_MODE + flags |= ClimateEntityFeature.FAN_MODE return flags async def async_set_fan_mode(self, fan_mode): @@ -179,7 +171,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): # the entity state ahead of receiving the confirming push updates self.async_schedule_update_ha_state(True) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" mode = STATE_TO_MODE[hvac_mode] await self._device.set_thermostat_mode(mode, set_status=True) @@ -199,9 +191,9 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): # Heat/cool setpoint heating_setpoint = None cooling_setpoint = None - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: heating_setpoint = kwargs.get(ATTR_TEMPERATURE) - elif self.hvac_mode == HVAC_MODE_COOL: + elif self.hvac_mode == HVACMode.COOL: cooling_setpoint = kwargs.get(ATTR_TEMPERATURE) else: heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW) @@ -280,47 +272,42 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): return self._device.status.supported_thermostat_fan_modes @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" return OPERATING_STATE_TO_ACTION.get( self._device.status.thermostat_operating_state ) @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" return self._hvac_mode @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return self._hvac_modes - @property - def supported_features(self): - """Return the supported features.""" - return self._supported_features - @property def target_temperature(self): """Return the temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: return self._device.status.cooling_setpoint - if self.hvac_mode == HVAC_MODE_HEAT: + if self.hvac_mode == HVACMode.HEAT: return self._device.status.heating_setpoint return None @property def target_temperature_high(self): """Return the highbound target temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self._device.status.cooling_setpoint return None @property def target_temperature_low(self): """Return the lowbound target temperature we try to reach.""" - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: return self._device.status.heating_setpoint return None @@ -333,6 +320,10 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): """Define a SmartThings Air Conditioner.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) + def __init__(self, device): """Init the class.""" super().__init__(device) @@ -345,9 +336,9 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): # the entity state ahead of receiving the confirming push updates self.async_write_ha_state() - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.async_turn_off() return tasks = [] @@ -369,7 +360,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): tasks = [] # operation mode if operation_mode := kwargs.get(ATTR_HVAC_MODE): - if operation_mode == HVAC_MODE_OFF: + if operation_mode == HVACMode.OFF: tasks.append(self._device.switch_off(set_status=True)) else: if not self._device.status.switch: @@ -400,7 +391,7 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): async def async_update(self): """Update the calculated fields of the AC.""" - modes = {HVAC_MODE_OFF} + modes = {HVACMode.OFF} for mode in self._device.status.supported_ac_modes: if (state := AC_MODE_TO_STATE.get(mode)) is not None: modes.add(state) @@ -454,22 +445,17 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): return self._device.status.supported_ac_fan_modes @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode | None: """Return current operation ie. heat, cool, idle.""" if not self._device.status.switch: - return HVAC_MODE_OFF + return HVACMode.OFF return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode) @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return self._hvac_modes - @property - def supported_features(self): - """Return the supported features.""" - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - @property def target_temperature(self): """Return the temperature we try to reach.""" diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index e029143f7f3..578b13879e8 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -12,11 +12,9 @@ from homeassistant.components.cover import ( STATE_CLOSING, STATE_OPEN, STATE_OPENING, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_BATTERY_LEVEL @@ -77,9 +75,11 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity): self._device_class = None self._state = None self._state_attrs = None - self._supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + self._attr_supported_features = ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + ) if Capability.switch_level in device.capabilities: - self._supported_features |= SUPPORT_SET_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_POSITION async def async_close_cover(self, **kwargs): """Close cover.""" @@ -99,7 +99,7 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity): async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - if not self._supported_features & SUPPORT_SET_POSITION: + if not self._attr_supported_features & CoverEntityFeature.SET_POSITION: return # Do not set_status=True as device will report progress. await self._device.set_level(kwargs[ATTR_POSITION], 0) @@ -144,7 +144,7 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity): @property def current_cover_position(self): """Return current position of cover.""" - if not self._supported_features & SUPPORT_SET_POSITION: + if not self._attr_supported_features & CoverEntityFeature.SET_POSITION: return None return self._device.status.level @@ -157,8 +157,3 @@ class SmartThingsCover(SmartThingsEntity, CoverEntity): def extra_state_attributes(self): """Get additional state attributes.""" return self._state_attrs - - @property - def supported_features(self): - """Flag supported features.""" - return self._supported_features diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 019cd5dc7b6..d3f3affa358 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -6,7 +6,7 @@ import math from pysmartthings import Capability -from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -50,6 +50,8 @@ def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: class SmartThingsFan(SmartThingsEntity, FanEntity): """Define a SmartThings Fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + async def async_set_percentage(self, percentage: int | None) -> None: """Set the speed percentage of the fan.""" if percentage is None: @@ -93,8 +95,3 @@ class SmartThingsFan(SmartThingsEntity, FanEntity): def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return int_states_in_range(SPEED_RANGE) - - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED diff --git a/homeassistant/components/smartthings/translations/it.json b/homeassistant/components/smartthings/translations/it.json index 4f2006e52c3..258abb0da8d 100644 --- a/homeassistant/components/smartthings/translations/it.json +++ b/homeassistant/components/smartthings/translations/it.json @@ -19,8 +19,8 @@ "data": { "access_token": "Token di accesso" }, - "description": "Inserisci un SmartThings [Personal Access Token]({token_url}) che \u00e8 stato creato secondo le [istruzioni]({component_url}). Questo sar\u00e0 utilizzato per creare l'integrazione Home Assistant all'interno del tuo account SmartThings.", - "title": "Inserisci il Token di Accesso Personale" + "description": "Inserisci un [token di accesso personale]({token_url}) che \u00e8 stato creato secondo le [istruzioni]({component_url}). Questo sar\u00e0 utilizzato per creare l'integrazione Home Assistant all'interno del tuo account SmartThings.", + "title": "Inserisci il token di accesso personale" }, "select_location": { "data": { diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index c39bcc2ab51..8789c33bc9a 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -1,15 +1,15 @@ """Platform for climate integration.""" +from __future__ import annotations + from smarttub import Spa from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_HEAT, PRESET_ECO, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -31,8 +31,8 @@ PRESET_MODES = { HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} HVAC_ACTIONS = { - "OFF": CURRENT_HVAC_IDLE, - "ON": CURRENT_HVAC_HEAT, + "OFF": HVACAction.IDLE, + "ON": HVACAction.HEATING, } @@ -53,6 +53,15 @@ async def async_setup_entry( class SmartTubThermostat(SmartTubEntity, ClimateEntity): """The target water temperature for the spa.""" + # SmartTub devices don't seem to have the option of disabling the heater, + # so this is always HVACMode.HEAT. + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] + # Only target temperature is supported. + _attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) + def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__(coordinator, spa, "Thermostat") @@ -63,30 +72,16 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): return TEMP_CELSIUS @property - def hvac_action(self): + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation.""" return HVAC_ACTIONS.get(self.spa_status.heater) - @property - def hvac_modes(self): - """Return the list of available hvac operation modes.""" - return [HVAC_MODE_HEAT] - - @property - def hvac_mode(self): - """Return the current hvac mode. - - SmartTub devices don't seem to have the option of disabling the heater, - so this is always HVAC_MODE_HEAT. - """ - return HVAC_MODE_HEAT - - async def async_set_hvac_mode(self, hvac_mode: str): + async def async_set_hvac_mode(self, hvac_mode: HVACMode): """Set new target hvac mode. As with hvac_mode, we don't really have an option here. """ - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: return raise NotImplementedError(hvac_mode) @@ -102,14 +97,6 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): max_temp = DEFAULT_MAX_TEMP return convert_temperature(max_temp, TEMP_CELSIUS, self.temperature_unit) - @property - def supported_features(self): - """Return the set of supported features. - - Only target temperature is supported. - """ - return SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE - @property def preset_mode(self): """Return the current preset mode.""" diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index e62819f122c..5d68b90145f 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -94,7 +94,6 @@ class SmartTubController: return data async def _get_spa_data(self, spa): - # pylint: disable=no-self-use full_status, reminders, errors = await asyncio.gather( spa.get_status_full(), spa.get_reminders(), diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py index 8c6010568e2..b8750c50611 100644 --- a/homeassistant/components/smarttub/light.py +++ b/homeassistant/components/smarttub/light.py @@ -5,9 +5,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, EFFECT_COLORLOOP, - SUPPORT_BRIGHTNESS, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -43,6 +43,10 @@ async def async_setup_entry( class SmartTubLight(SmartTubEntity, LightEntity): """A light on a spa.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = LightEntityFeature.EFFECT + def __init__(self, coordinator, light): """Initialize the entity.""" super().__init__(coordinator, light.spa, "light") @@ -86,11 +90,6 @@ class SmartTubLight(SmartTubEntity, LightEntity): """Return true if the light is on.""" return self.light.mode != SpaLight.LightMode.OFF - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_EFFECT - @property def effect(self): """Return the current effect.""" diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json index 90f85b6c839..1d7500b9185 100644 --- a/homeassistant/components/smarttub/manifest.json +++ b/homeassistant/components/smarttub/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/smarttub", "dependencies": [], "codeowners": ["@mdz"], - "requirements": ["python-smarttub==0.0.30"], + "requirements": ["python-smarttub==0.0.32"], "quality_scale": "platinum", "iot_class": "cloud_polling", "loggers": ["smarttub"] diff --git a/homeassistant/components/smarty/fan.py b/homeassistant/components/smarty/fan.py index 98df91acbc3..531b96d2558 100644 --- a/homeassistant/components/smarty/fan.py +++ b/homeassistant/components/smarty/fan.py @@ -4,7 +4,7 @@ from __future__ import annotations import logging import math -from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -40,6 +40,8 @@ async def async_setup_platform( class SmartyFan(FanEntity): """Representation of a Smarty Fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + def __init__(self, name, smarty): """Initialize the entity.""" self._name = name @@ -61,11 +63,6 @@ class SmartyFan(FanEntity): """Return the icon to use in the frontend.""" return "mdi:air-conditioner" - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_SET_SPEED - @property def is_on(self): """Return state of the fan.""" diff --git a/homeassistant/components/smhi/translations/hu.json b/homeassistant/components/smhi/translations/hu.json index 425cf927631..e83ea03a193 100644 --- a/homeassistant/components/smhi/translations/hu.json +++ b/homeassistant/components/smhi/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, "error": { "name_exists": "A n\u00e9v m\u00e1r l\u00e9tezik", "wrong_location": "Csak sv\u00e9dorsz\u00e1gi helysz\u00edn megengedett" @@ -9,7 +12,7 @@ "data": { "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "title": "Helysz\u00edn Sv\u00e9dorsz\u00e1gban" } diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index b88b81d1fb4..bd8d2f365c9 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -73,7 +73,6 @@ class Gateway: self._hass.add_job(self._notify_incoming_sms, data) - # pylint: disable=no-self-use def get_and_delete_all_sms(self, state_machine, force=False): """Read and delete all SMS in the modem.""" # Read SMS memory status ... diff --git a/homeassistant/components/smtp/manifest.json b/homeassistant/components/smtp/manifest.json index f7a3373ce30..526719615bc 100644 --- a/homeassistant/components/smtp/manifest.json +++ b/homeassistant/components/smtp/manifest.json @@ -2,6 +2,6 @@ "domain": "smtp", "name": "SMTP", "documentation": "https://www.home-assistant.io/integrations/smtp", - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "cloud_push" } diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py index 4ddf6988add..ad71266d65c 100644 --- a/homeassistant/components/snapcast/media_player.py +++ b/homeassistant/components/snapcast/media_player.py @@ -8,11 +8,10 @@ import snapcast.control from snapcast.control.server import CONTROL_PORT import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -45,13 +44,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -SUPPORT_SNAPCAST_CLIENT = ( - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_SELECT_SOURCE -) -SUPPORT_SNAPCAST_GROUP = ( - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT): cv.port} ) @@ -124,6 +116,12 @@ async def handle_set_latency(entity, service_call): class SnapcastGroupDevice(MediaPlayerEntity): """Representation of a Snapcast group device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, group, uid_part): """Initialize the Snapcast group device.""" group.set_callback(self.schedule_update_ha_state) @@ -164,11 +162,6 @@ class SnapcastGroupDevice(MediaPlayerEntity): """Volume muted.""" return self._group.muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_SNAPCAST_GROUP - @property def source_list(self): """List of available input sources.""" @@ -215,6 +208,12 @@ class SnapcastGroupDevice(MediaPlayerEntity): class SnapcastClientDevice(MediaPlayerEntity): """Representation of a Snapcast client device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + def __init__(self, client, uid_part): """Initialize the Snapcast client device.""" client.set_callback(self.schedule_update_ha_state) @@ -255,11 +254,6 @@ class SnapcastClientDevice(MediaPlayerEntity): """Volume muted.""" return self._client.muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_SNAPCAST_CLIENT - @property def source_list(self): """List of available input sources.""" diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index 76df9e18606..ef0213e82dc 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -2,7 +2,7 @@ "domain": "snmp", "name": "SNMP", "documentation": "https://www.home-assistant.io/integrations/snmp", - "requirements": ["pysnmp==4.4.12"], + "requirements": ["pysnmplib==5.0.10"], "codeowners": [], "iot_class": "local_polling", "loggers": ["pyasn1", "pysmi", "pysnmp"] diff --git a/homeassistant/components/solax/translations/es.json b/homeassistant/components/solax/translations/es.json new file mode 100644 index 00000000000..4728aed6395 --- /dev/null +++ b/homeassistant/components/solax/translations/es.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Error inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/cover.py b/homeassistant/components/soma/cover.py index e9c413a06b4..abc6d828acc 100644 --- a/homeassistant/components/soma/cover.py +++ b/homeassistant/components/soma/cover.py @@ -4,17 +4,9 @@ from __future__ import annotations from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - DEVICE_CLASS_BLIND, - DEVICE_CLASS_SHADE, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, + CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -49,12 +41,12 @@ async def async_setup_entry( class SomaTilt(SomaEntity, CoverEntity): """Representation of a Soma Tilt device.""" - _attr_device_class = DEVICE_CLASS_BLIND + _attr_device_class = CoverDeviceClass.BLIND _attr_supported_features = ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_STOP_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION ) @property @@ -123,9 +115,12 @@ class SomaTilt(SomaEntity, CoverEntity): class SomaShade(SomaEntity, CoverEntity): """Representation of a Soma Shade device.""" - _attr_device_class = DEVICE_CLASS_SHADE + _attr_device_class = CoverDeviceClass.SHADE _attr_supported_features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION ) @property diff --git a/homeassistant/components/soma/translations/it.json b/homeassistant/components/soma/translations/it.json index c3cd51e6351..00d454e2c96 100644 --- a/homeassistant/components/soma/translations/it.json +++ b/homeassistant/components/soma/translations/it.json @@ -4,7 +4,7 @@ "already_setup": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "connection_error": "Impossibile connettersi", - "missing_configuration": "Il componente Soma non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente Soma non \u00e8 configurato. Segui la documentazione.", "result_error": "SOMA Connect ha risposto con stato di errore." }, "create_entry": { diff --git a/homeassistant/components/somfy/climate.py b/homeassistant/components/somfy/climate.py index 1e1e3cdca95..1384574c3d3 100644 --- a/homeassistant/components/somfy/climate.py +++ b/homeassistant/components/somfy/climate.py @@ -12,14 +12,11 @@ from pymfy.api.devices.thermostat import ( from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, PRESET_AWAY, PRESET_HOME, PRESET_SLEEP, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -45,7 +42,7 @@ PRESETS_MAPPING = { } REVERSE_PRESET_MAPPING = {v: k for k, v in PRESETS_MAPPING.items()} -HVAC_MODES_MAPPING = {HvacState.COOL: HVAC_MODE_COOL, HvacState.HEAT: HVAC_MODE_HEAT} +HVAC_MODES_MAPPING = {HvacState.COOL: HVACMode.COOL, HvacState.HEAT: HVACMode.HEAT} async def async_setup_entry( @@ -69,6 +66,10 @@ async def async_setup_entry( class SomfyClimate(SomfyEntity, ClimateEntity): """Representation of a Somfy thermostat device.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, coordinator, device_id): """Initialize the Somfy device.""" super().__init__(coordinator, device_id) @@ -79,11 +80,6 @@ class SomfyClimate(SomfyEntity, ClimateEntity): """Update the device with the latest data.""" self._climate = Thermostat(self.device, self.coordinator.client) - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - @property def temperature_unit(self): """Return the unit of measurement used by the platform.""" @@ -122,25 +118,25 @@ class SomfyClimate(SomfyEntity, ClimateEntity): return self._climate.get_humidity() @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" if self._climate.get_regulation_state() == RegulationState.TIMETABLE: - return HVAC_MODE_AUTO + return HVACMode.AUTO return HVAC_MODES_MAPPING[self._climate.get_hvac_state()] @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes. HEAT and COOL mode are exclusive. End user has to enable a mode manually within the Somfy application. So only one mode can be displayed. Auto mode is a scheduler. """ hvac_state = HVAC_MODES_MAPPING[self._climate.get_hvac_state()] - return [HVAC_MODE_AUTO, hvac_state] + return [HVACMode.AUTO, hvac_state] - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_AUTO: + if hvac_mode == HVACMode.AUTO: self._climate.cancel_target() else: self._climate.set_target( diff --git a/homeassistant/components/somfy/cover.py b/homeassistant/components/somfy/cover.py index 91bebc1da0d..a2a72d4ce98 100644 --- a/homeassistant/components/somfy/cover.py +++ b/homeassistant/components/somfy/cover.py @@ -9,16 +9,9 @@ from pymfy.api.devices.category import Category from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_OPTIMISTIC, STATE_CLOSED, STATE_OPEN @@ -82,19 +75,19 @@ class SomfyCover(SomfyEntity, RestoreEntity, CoverEntity): """Flag supported features.""" supported_features = 0 if self.has_capability("open"): - supported_features |= SUPPORT_OPEN + supported_features |= CoverEntityFeature.OPEN if self.has_capability("close"): - supported_features |= SUPPORT_CLOSE + supported_features |= CoverEntityFeature.CLOSE if self.has_capability("stop"): - supported_features |= SUPPORT_STOP + supported_features |= CoverEntityFeature.STOP if self.has_capability("position"): - supported_features |= SUPPORT_SET_POSITION + supported_features |= CoverEntityFeature.SET_POSITION if self.has_capability("rotation"): supported_features |= ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_STOP_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION ) return supported_features diff --git a/homeassistant/components/somfy/manifest.json b/homeassistant/components/somfy/manifest.json index 144938b1822..76e3d281228 100644 --- a/homeassistant/components/somfy/manifest.json +++ b/homeassistant/components/somfy/manifest.json @@ -3,7 +3,7 @@ "name": "Somfy", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/somfy", - "dependencies": ["http"], + "dependencies": ["auth"], "codeowners": ["@tetienne"], "requirements": ["pymfy==0.11.0"], "zeroconf": [ diff --git a/homeassistant/components/somfy/translations/hu.json b/homeassistant/components/somfy/translations/hu.json index 06b0894faf1..96b873b2c42 100644 --- a/homeassistant/components/somfy/translations/hu.json +++ b/homeassistant/components/somfy/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "create_entry": { @@ -11,7 +11,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/somfy/translations/it.json b/homeassistant/components/somfy/translations/it.json index 1c6650232dc..0201e1e2569 100644 --- a/homeassistant/components/somfy/translations/it.json +++ b/homeassistant/components/somfy/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, diff --git a/homeassistant/components/sonarr/translations/cs.json b/homeassistant/components/sonarr/translations/cs.json index 00ff1611319..9be2106b4e2 100644 --- a/homeassistant/components/sonarr/translations/cs.json +++ b/homeassistant/components/sonarr/translations/cs.json @@ -21,6 +21,7 @@ "host": "Hostitel", "port": "Port", "ssl": "Pou\u017e\u00edv\u00e1 SSL certifik\u00e1t", + "url": "URL", "verify_ssl": "Ov\u011b\u0159it certifik\u00e1t SSL" } } diff --git a/homeassistant/components/sonarr/translations/hu.json b/homeassistant/components/sonarr/translations/hu.json index c3cc2ac0f6c..5aabd38a974 100644 --- a/homeassistant/components/sonarr/translations/hu.json +++ b/homeassistant/components/sonarr/translations/hu.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "reauth_confirm": { - "description": "A Sonarr integr\u00e1ci\u00f3t manu\u00e1lisan kell hiteles\u00edteni: {host}", + "description": "A Sonarr-integr\u00e1ci\u00f3t manu\u00e1lisan \u00fajra kell hiteles\u00edteni a Sonarr API-n\u00e1l: {url}", "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" }, "user": { diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index e161f818c8c..fed1762bb44 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -16,14 +16,9 @@ from songpal import ( ) import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON @@ -45,15 +40,6 @@ _LOGGER = logging.getLogger(__name__) PARAM_NAME = "name" PARAM_VALUE = "value" -SUPPORT_SONGPAL = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_SELECT_SOURCE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF -) - INITIAL_RETRY_DELAY = 10 @@ -103,6 +89,15 @@ async def async_setup_entry( class SongpalEntity(MediaPlayerEntity): """Class representing a Songpal device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + ) + def __init__(self, name, device): """Init.""" self._name = name @@ -354,8 +349,3 @@ class SongpalEntity(MediaPlayerEntity): def is_volume_muted(self): """Return whether the device is muted.""" return self._is_muted - - @property - def supported_features(self): - """Return supported features.""" - return SUPPORT_SONGPAL diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index bb4c61fdadf..114c2815a56 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -4,7 +4,6 @@ from __future__ import annotations import asyncio from collections import OrderedDict import datetime -from enum import Enum from functools import partial import logging import socket @@ -13,7 +12,7 @@ from urllib.parse import urlparse from soco import events_asyncio import soco.config as soco_config from soco.core import SoCo -from soco.exceptions import NotSupportedException, SoCoException +from soco.exceptions import SoCoException import voluptuous as vol from homeassistant import config_entries @@ -23,7 +22,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOSTS, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_send from homeassistant.helpers.event import async_track_time_interval, call_later from homeassistant.helpers.typing import ConfigType @@ -74,14 +73,6 @@ CONFIG_SCHEMA = vol.Schema( ) -class SoCoCreationSource(Enum): - """Represent the creation source of a SoCo instance.""" - - CONFIGURED = "configured" - DISCOVERED = "discovered" - REBOOTED = "rebooted" - - class SonosData: """Storage class for platform global data.""" @@ -93,7 +84,6 @@ class SonosData: self.alarms: dict[str, SonosAlarms] = {} self.topology_condition = asyncio.Condition() self.hosts_heartbeat = None - self.discovery_ignored: set[str] = set() self.discovery_known: set[str] = set() self.boot_counts: dict[str, int] = {} self.mdns_names: dict[str, str] = {} @@ -165,37 +155,35 @@ class SonosDiscoveryManager: self.hass = hass self.entry = entry self.data = data - self.hosts = hosts + self.hosts = set(hosts) self.discovery_lock = asyncio.Lock() + self._known_invisible = set() + self._manual_config_required = bool(hosts) async def async_shutdown(self): """Stop all running tasks.""" await self._async_stop_event_listener() self._stop_manual_heartbeat() - def _create_soco(self, ip_address: str, source: SoCoCreationSource) -> SoCo | None: - """Create a soco instance and return if successful.""" - if ip_address in self.data.discovery_ignored: - return None + def is_device_invisible(self, ip_address: str) -> bool: + """Check if device at provided IP is known to be invisible.""" + return any(x for x in self._known_invisible if x.ip_address == ip_address) + def _create_visible_speakers(self, ip_address: str) -> None: + """Create all visible SonosSpeaker instances with the provided seed IP.""" try: soco = SoCo(ip_address) - # Ensure that the player is available and UID is cached - uid = soco.uid - # Abort early if the device is not visible - if not soco.is_visible: - return None - _ = soco.volume - return soco - except NotSupportedException as exc: - # pylint: disable-next=used-before-assignment - _LOGGER.debug("Device %s is not supported, ignoring: %s", uid, exc) - self.data.discovery_ignored.add(ip_address) + visible_zones = soco.visible_zones + self._known_invisible = soco.all_zones - visible_zones except (OSError, SoCoException) as ex: _LOGGER.warning( - "Failed to connect to %s player '%s': %s", source.value, ip_address, ex + "Failed to request visible zones from %s: %s", ip_address, ex ) - return None + return + + for zone in visible_zones: + if zone.uid not in self.data.discovered: + self._add_speaker(zone) async def _async_stop_event_listener(self, event: Event | None = None) -> None: for speaker in self.data.discovered.values(): @@ -212,10 +200,12 @@ class SonosDiscoveryManager: self.data.hosts_heartbeat() self.data.hosts_heartbeat = None - def _discovered_player(self, soco: SoCo) -> None: - """Handle a (re)discovered player.""" + def _add_speaker(self, soco: SoCo) -> None: + """Create and set up a new SonosSpeaker instance.""" try: speaker_info = soco.get_speaker_info(True) + if soco.uid not in self.data.boot_counts: + self.data.boot_counts[soco.uid] = soco.boot_seqnum _LOGGER.debug("Adding new speaker: %s", speaker_info) speaker = SonosSpeaker(self.hass, soco, speaker_info) self.data.discovered[soco.uid] = speaker @@ -231,45 +221,87 @@ class SonosDiscoveryManager: except (OSError, SoCoException): _LOGGER.warning("Failed to add SonosSpeaker using %s", soco, exc_info=True) - def _manual_hosts(self, now: datetime.datetime | None = None) -> None: - """Players from network configuration.""" + def _poll_manual_hosts(self, now: datetime.datetime | None = None) -> None: + """Add and maintain Sonos devices from a manual configuration.""" for host in self.hosts: ip_addr = socket.gethostbyname(host) - known_uid = next( + soco = SoCo(ip_addr) + try: + visible_zones = soco.visible_zones + except OSError: + _LOGGER.warning("Could not get visible Sonos devices from %s", ip_addr) + else: + if new_hosts := { + x.ip_address + for x in visible_zones + if x.ip_address not in self.hosts + }: + _LOGGER.debug("Adding to manual hosts: %s", new_hosts) + self.hosts.update(new_hosts) + dispatcher_send( + self.hass, + f"{SONOS_SPEAKER_ACTIVITY}-{soco.uid}", + "manual zone scan", + ) + break + + for host in self.hosts.copy(): + ip_addr = socket.gethostbyname(host) + if self.is_device_invisible(ip_addr): + _LOGGER.debug("Discarding %s from manual hosts", ip_addr) + self.hosts.discard(ip_addr) + continue + + known_speaker = next( ( - uid - for uid, speaker in self.data.discovered.items() + speaker + for speaker in self.data.discovered.values() if speaker.soco.ip_address == ip_addr ), None, ) - if not known_uid: - if soco := self._create_soco(ip_addr, SoCoCreationSource.CONFIGURED): - self._discovered_player(soco) + if not known_speaker: + self._create_visible_speakers(ip_addr) + elif not known_speaker.available: + try: + known_speaker.soco.renderingControl.GetVolume( + [("InstanceID", 0), ("Channel", "Master")], timeout=1 + ) + except OSError: + _LOGGER.debug( + "Manual poll to %s failed, keeping unavailable", ip_addr + ) + else: + dispatcher_send( + self.hass, + f"{SONOS_SPEAKER_ACTIVITY}-{known_speaker.uid}", + "manual rediscovery", + ) self.data.hosts_heartbeat = call_later( - self.hass, DISCOVERY_INTERVAL.total_seconds(), self._manual_hosts + self.hass, DISCOVERY_INTERVAL.total_seconds(), self._poll_manual_hosts ) - def _discovered_ip(self, ip_address): - if soco := self._create_soco(ip_address, SoCoCreationSource.DISCOVERED): - self._discovered_player(soco) - - async def _async_create_discovered_player(self, uid, discovered_ip, boot_seqnum): - """Only create one player at a time.""" + async def _async_handle_discovery_message( + self, uid: str, discovered_ip: str, boot_seqnum: int + ) -> None: + """Handle discovered player creation and activity.""" async with self.discovery_lock: - if uid not in self.data.discovered: + if not self.data.discovered: + # Initial discovery, attempt to add all visible zones await self.hass.async_add_executor_job( - self._discovered_ip, discovered_ip + self._create_visible_speakers, + discovered_ip, ) - return - - if boot_seqnum and boot_seqnum > self.data.boot_counts[uid]: + elif uid not in self.data.discovered: + if self.is_device_invisible(discovered_ip): + return + await self.hass.async_add_executor_job( + self._add_speaker, SoCo(discovered_ip) + ) + elif boot_seqnum and boot_seqnum > self.data.boot_counts[uid]: self.data.boot_counts[uid] = boot_seqnum - if soco := await self.hass.async_add_executor_job( - self._create_soco, discovered_ip, SoCoCreationSource.REBOOTED - ): - async_dispatcher_send(self.hass, f"{SONOS_REBOOTED}-{uid}", soco) + async_dispatcher_send(self.hass, f"{SONOS_REBOOTED}-{uid}") else: async_dispatcher_send( self.hass, f"{SONOS_SPEAKER_ACTIVITY}-{uid}", "discovery" @@ -308,9 +340,17 @@ class SonosDiscoveryManager: self, source, info, discovered_ip, uid, boot_seqnum, model, mdns_name ): """Handle discovery via ssdp or zeroconf.""" + if self._manual_config_required: + _LOGGER.warning( + "Automatic discovery is working, Sonos hosts in configuration.yaml are not needed" + ) + self._manual_config_required = False if model in DISCOVERY_IGNORED_MODELS: _LOGGER.debug("Ignoring device: %s", info) return + if self.is_device_invisible(discovered_ip): + return + if boot_seqnum: boot_seqnum = int(boot_seqnum) self.data.boot_counts.setdefault(uid, boot_seqnum) @@ -321,7 +361,7 @@ class SonosDiscoveryManager: _LOGGER.debug("New %s discovery uid=%s: %s", source, uid, info) self.data.discovery_known.add(uid) asyncio.create_task( - self._async_create_discovered_player(uid, discovered_ip, boot_seqnum) + self._async_handle_discovery_message(uid, discovered_ip, boot_seqnum) ) async def setup_platforms_and_discovery(self): @@ -344,7 +384,7 @@ class SonosDiscoveryManager: EVENT_HOMEASSISTANT_STOP, self._stop_manual_heartbeat ) ) - await self.hass.async_add_executor_job(self._manual_hosts) + await self.hass.async_add_executor_job(self._poll_manual_hosts) self.entry.async_on_unload( await ssdp.async_register_callback( diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index 7b85828377a..e6a5225e30e 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -152,6 +152,7 @@ SONOS_CHECK_ACTIVITY = "sonos_check_activity" SONOS_CREATE_ALARM = "sonos_create_alarm" SONOS_CREATE_AUDIO_FORMAT_SENSOR = "sonos_create_audio_format_sensor" SONOS_CREATE_BATTERY = "sonos_create_battery" +SONOS_CREATE_FAVORITES_SENSOR = "sonos_create_favorites_sensor" SONOS_CREATE_MIC_SENSOR = "sonos_create_mic_sensor" SONOS_CREATE_SWITCHES = "sonos_create_switches" SONOS_CREATE_LEVELS = "sonos_create_levels" diff --git a/homeassistant/components/sonos/diagnostics.py b/homeassistant/components/sonos/diagnostics.py index 421a0f7ed6a..077ca3a68cd 100644 --- a/homeassistant/components/sonos/diagnostics.py +++ b/homeassistant/components/sonos/diagnostics.py @@ -49,7 +49,7 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" payload = {"current_timestamp": time.monotonic()} - for section in ("discovered", "discovery_known", "discovery_ignored"): + for section in ("discovered", "discovery_known"): payload[section] = {} data = getattr(hass.data[DATA_SONOS], section) if isinstance(data, set): diff --git a/homeassistant/components/sonos/entity.py b/homeassistant/components/sonos/entity.py index 8565fe08e9c..ba1f72cd56b 100644 --- a/homeassistant/components/sonos/entity.py +++ b/homeassistant/components/sonos/entity.py @@ -13,13 +13,7 @@ import homeassistant.helpers.device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, Entity -from .const import ( - DATA_SONOS, - DOMAIN, - SONOS_FALLBACK_POLL, - SONOS_FAVORITES_UPDATED, - SONOS_STATE_UPDATED, -) +from .const import DATA_SONOS, DOMAIN, SONOS_FALLBACK_POLL, SONOS_STATE_UPDATED from .exception import SonosUpdateError from .speaker import SonosSpeaker @@ -54,13 +48,6 @@ class SonosEntity(Entity): self.async_write_ha_state, ) ) - self.async_on_remove( - async_dispatcher_connect( - self.hass, - f"{SONOS_FAVORITES_UPDATED}-{self.soco.household_id}", - self.async_write_ha_state, - ) - ) async def async_will_remove_from_hass(self) -> None: """Clean up when entity is removed.""" diff --git a/homeassistant/components/sonos/favorites.py b/homeassistant/components/sonos/favorites.py index fd651b7740c..5284a5f6745 100644 --- a/homeassistant/components/sonos/favorites.py +++ b/homeassistant/components/sonos/favorites.py @@ -11,9 +11,9 @@ from soco.data_structures import DidlFavorite from soco.events_base import Event as SonosEvent from soco.exceptions import SoCoException -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_send -from .const import SONOS_FAVORITES_UPDATED +from .const import SONOS_CREATE_FAVORITES_SENSOR, SONOS_FAVORITES_UPDATED from .helpers import soco_error from .household_coordinator import SonosHouseholdCoordinator @@ -37,6 +37,16 @@ class SonosFavorites(SonosHouseholdCoordinator): favorites = self._favorites.copy() return iter(favorites) + def setup(self, soco: SoCo) -> None: + """Override to send a signal on base class setup completion.""" + super().setup(soco) + dispatcher_send(self.hass, SONOS_CREATE_FAVORITES_SENSOR, self) + + @property + def count(self) -> int: + """Return the number of favorites.""" + return len(self._favorites) + def lookup_by_item_id(self, item_id: str) -> DidlFavorite | None: """Return the favorite object with the provided item_id.""" return next((fav for fav in self._favorites if fav.item_id == item_id), None) diff --git a/homeassistant/components/sonos/media.py b/homeassistant/components/sonos/media.py index f4108b85317..1b4dbd00d59 100644 --- a/homeassistant/components/sonos/media.py +++ b/homeassistant/components/sonos/media.py @@ -172,7 +172,8 @@ class SonosMedia: if et_uri_md: self.channel = et_uri_md.title - if ct_md and ct_md.radio_show: + # Extra guards for S1 compatibility + if ct_md and hasattr(ct_md, "radio_show") and ct_md.radio_show: radio_show = ct_md.radio_show.split(",")[0] self.channel = " • ".join(filter(None, [self.channel, radio_show])) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 16d805a578a..f7f5e2722ff 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -19,6 +19,7 @@ import voluptuous as vol from homeassistant.components import media_source, spotify from homeassistant.components.media_player import ( MediaPlayerEntity, + MediaPlayerEntityFeature, async_process_play_media_url, ) from homeassistant.components.media_player.const import ( @@ -32,21 +33,6 @@ from homeassistant.components.media_player.const import ( REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_GROUPING, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, ) from homeassistant.components.plex.const import PLEX_URI_SCHEME from homeassistant.components.plex.services import process_plex_payload @@ -80,24 +66,6 @@ from .speaker import SonosMedia, SonosSpeaker _LOGGER = logging.getLogger(__name__) -SUPPORT_SONOS = ( - SUPPORT_BROWSE_MEDIA - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_GROUPING - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_REPEAT_SET - | SUPPORT_SEEK - | SUPPORT_SELECT_SOURCE - | SUPPORT_SHUFFLE_SET - | SUPPORT_STOP - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET -) - VOLUME_INCREMENT = 2 REPEAT_TO_SONOS = { @@ -108,8 +76,6 @@ REPEAT_TO_SONOS = { SONOS_TO_REPEAT = {meaning: mode for mode, meaning in REPEAT_TO_SONOS.items()} -ATTR_SONOS_GROUP = "sonos_group" - UPNP_ERRORS_TO_IGNORE = ["701", "711", "712"] SERVICE_JOIN = "join" @@ -250,7 +216,23 @@ async def async_setup_entry( class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): """Representation of a Sonos entity.""" - _attr_supported_features = SUPPORT_SONOS + _attr_supported_features = ( + MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.GROUPING + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + ) _attr_media_content_type = MEDIA_TYPE_MUSIC def __init__(self, speaker: SonosSpeaker) -> None: @@ -276,11 +258,25 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): if self.coordinator.uid == uid: self.async_write_ha_state() + @property + def available(self) -> bool: + """Return if the media_player is available.""" + return ( + self.speaker.available + and self.speaker.sonos_group_entities + and self.media.playback_status + ) + @property def coordinator(self) -> SonosSpeaker: """Return the current coordinator SonosSpeaker.""" return self.speaker.coordinator or self.speaker + @property + def group_members(self) -> list[str] | None: + """List of entity_ids which are currently grouped together.""" + return self.speaker.sonos_group_entities + def __hash__(self) -> int: """Return a hash of self.""" return hash(self.unique_id) @@ -670,9 +666,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): @property def extra_state_attributes(self) -> dict[str, Any]: """Return entity specific state attributes.""" - attributes: dict[str, Any] = { - ATTR_SONOS_GROUP: self.speaker.sonos_group_entities - } + attributes: dict[str, Any] = {} if self.media.queue_position is not None: attributes[ATTR_QUEUE_POSITION] = self.media.queue_position diff --git a/homeassistant/components/sonos/sensor.py b/homeassistant/components/sonos/sensor.py index f011cb2d754..380d1a3b9b6 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -11,8 +11,15 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY, SOURCE_TV +from .const import ( + SONOS_CREATE_AUDIO_FORMAT_SENSOR, + SONOS_CREATE_BATTERY, + SONOS_CREATE_FAVORITES_SENSOR, + SONOS_FAVORITES_UPDATED, + SOURCE_TV, +) from .entity import SonosEntity, SonosPollingEntity +from .favorites import SonosFavorites from .helpers import soco_error from .speaker import SonosSpeaker @@ -40,6 +47,16 @@ async def async_setup_entry( entity = SonosBatteryEntity(speaker) async_add_entities([entity]) + @callback + def _async_create_favorites_sensor(favorites: SonosFavorites) -> None: + _LOGGER.debug( + "Creating favorites sensor (%s items) for household %s", + favorites.count, + favorites.household_id, + ) + entity = SonosFavoritesEntity(favorites) + async_add_entities([entity]) + config_entry.async_on_unload( async_dispatcher_connect( hass, SONOS_CREATE_AUDIO_FORMAT_SENSOR, _async_create_audio_format_entity @@ -51,6 +68,12 @@ async def async_setup_entry( ) ) + config_entry.async_on_unload( + async_dispatcher_connect( + hass, SONOS_CREATE_FAVORITES_SENSOR, _async_create_favorites_sensor + ) + ) + class SonosBatteryEntity(SonosEntity, SensorEntity): """Representation of a Sonos Battery entity.""" @@ -107,3 +130,36 @@ class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity): async def _async_fallback_poll(self) -> None: """Provide a stub for required ABC method.""" + + +class SonosFavoritesEntity(SensorEntity): + """Representation of a Sonos favorites info entity.""" + + _attr_entity_registry_enabled_default = False + _attr_icon = "mdi:star" + _attr_name = "Sonos Favorites" + _attr_native_unit_of_measurement = "items" + _attr_should_poll = False + + def __init__(self, favorites: SonosFavorites) -> None: + """Initialize the favorites sensor.""" + self.favorites = favorites + self._attr_unique_id = f"{favorites.household_id}-favorites" + + async def async_added_to_hass(self) -> None: + """Handle common setup when added to hass.""" + await self._async_update_state() + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{SONOS_FAVORITES_UPDATED}-{self.favorites.household_id}", + self._async_update_state, + ) + ) + + async def _async_update_state(self) -> None: + self._attr_native_value = self.favorites.count + self._attr_extra_state_attributes = { + "items": {fav.item_id: fav.title for fav in self.favorites} + } + self.async_write_ha_state() diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index ba4fec0cf57..4e4661b389b 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -189,7 +189,12 @@ class SonosSpeaker: def setup(self, entry: ConfigEntry) -> None: """Run initial setup of the speaker.""" - self.set_basic_info() + self.media.play_mode = self.soco.play_mode + self.update_volume() + self.update_groups() + if self.is_coordinator: + self.media.poll_media() + future = asyncio.run_coroutine_threadsafe( self.async_setup_dispatchers(entry), self.hass.loop ) @@ -247,11 +252,6 @@ class SonosSpeaker: """Write states for associated SonosEntity instances.""" async_dispatcher_send(self.hass, f"{SONOS_STATE_UPDATED}-{self.soco.uid}") - def set_basic_info(self) -> None: - """Set basic information when speaker is reconnected.""" - self.media.play_mode = self.soco.play_mode - self.update_volume() - # # Properties # @@ -456,6 +456,34 @@ class SonosSpeaker: @callback def async_dispatch_media_update(self, event: SonosEvent) -> None: """Update information about currently playing media from an event.""" + # The new coordinator can be provided in a media update event but + # before the ZoneGroupState updates. If this happens the playback + # state will be incorrect and should be ignored. Switching to the + # new coordinator will use its media. The regrouping process will + # be completed during the next ZoneGroupState update. + av_transport_uri = event.variables.get("av_transport_uri", "") + current_track_uri = event.variables.get("current_track_uri", "") + if av_transport_uri == current_track_uri and av_transport_uri.startswith( + "x-rincon:" + ): + new_coordinator_uid = av_transport_uri.split(":")[-1] + if new_coordinator_speaker := self.hass.data[DATA_SONOS].discovered.get( + new_coordinator_uid + ): + _LOGGER.debug( + "Media update coordinator (%s) received for %s", + new_coordinator_speaker.zone_name, + self.zone_name, + ) + self.coordinator = new_coordinator_speaker + else: + _LOGGER.debug( + "Media update coordinator (%s) for %s not yet available", + new_coordinator_uid, + self.zone_name, + ) + return + if crossfade := event.variables.get("current_crossfade_mode"): self.cross_fade = bool(int(crossfade)) @@ -582,15 +610,10 @@ class SonosSpeaker: ) await self.async_offline() - async def async_rebooted(self, soco: SoCo) -> None: + async def async_rebooted(self) -> None: """Handle a detected speaker reboot.""" - _LOGGER.debug( - "%s rebooted, reconnecting with %s", - self.zone_name, - soco, - ) + _LOGGER.debug("%s rebooted, reconnecting", self.zone_name) await self.async_offline() - self.soco = soco self.speaker_activity("reboot") # @@ -779,6 +802,7 @@ class SonosSpeaker: self.zone_name, uid, ) + return if self.sonos_group_entities == sonos_group_entities: # Useful in polling mode for speakers with stereo pairs or surrounds diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index 4ee3253c889..b5fea08e418 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -150,9 +150,6 @@ class SonosSwitchEntity(SonosPollingEntity, SwitchEntity): """Initialize the switch.""" super().__init__(speaker) self.feature_type = feature_type - self.entity_id = ENTITY_ID_FORMAT.format( - f"sonos_{speaker.zone_name}_{FRIENDLY_NAMES[feature_type]}" - ) self.needs_coordinator = feature_type in COORDINATOR_FEATURES self._attr_entity_category = EntityCategory.CONFIG self._attr_name = f"{speaker.zone_name} {FRIENDLY_NAMES[feature_type]}" diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index f8c398c1c88..3172eb4aed6 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -1,6 +1,7 @@ """Support for interface with a Bose Soundtouch.""" from __future__ import annotations +from functools import partial import logging import re @@ -8,19 +9,14 @@ from libsoundtouch import soundtouch_device from libsoundtouch.utils import Source import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components import media_source +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) +from homeassistant.components.media_player.browse_media import ( + async_process_play_media_url, ) from homeassistant.const import ( CONF_HOST, @@ -75,20 +71,6 @@ SOUNDTOUCH_REMOVE_ZONE_SCHEMA = vol.Schema( DEFAULT_NAME = "Bose Soundtouch" DEFAULT_PORT = 8090 -SUPPORT_SOUNDTOUCH = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_SET - | SUPPORT_TURN_ON - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_SELECT_SOURCE -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -144,11 +126,13 @@ def setup_platform( ] master = next( - [ - device - for device in hass.data[DATA_SOUNDTOUCH] - if device.entity_id == master_device_id - ].__iter__(), + iter( + [ + device + for device in hass.data[DATA_SOUNDTOUCH] + if device.entity_id == master_device_id + ] + ), None, ) @@ -199,6 +183,21 @@ def setup_platform( class SoundTouchDevice(MediaPlayerEntity): """Representation of a SoundTouch Bose device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) + def __init__(self, name, config): """Create Soundtouch Entity.""" @@ -264,11 +263,6 @@ class SoundTouchDevice(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._volume.muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_SOUNDTOUCH - def turn_off(self): """Turn off media player.""" self._device.power_off() @@ -360,6 +354,16 @@ class SoundTouchDevice(MediaPlayerEntity): EVENT_HOMEASSISTANT_START, async_update_on_start ) + async def async_play_media(self, media_type, media_id, **kwargs): + """Play a piece of media.""" + if media_source.is_media_source_id(media_id): + play_item = await media_source.async_resolve_media(self.hass, media_id) + media_id = async_process_play_media_url(self.hass, play_item.url) + + await self.hass.async_add_executor_job( + partial(self.play_media, media_type, media_id, **kwargs) + ) + def play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" _LOGGER.debug("Starting media with media_id: %s", media_id) @@ -371,9 +375,9 @@ class SoundTouchDevice(MediaPlayerEntity): # Preset presets = self._device.presets() preset = next( - [ - preset for preset in presets if preset.preset_id == str(media_id) - ].__iter__(), + iter( + [preset for preset in presets if preset.preset_id == str(media_id)] + ), None, ) if preset is not None: @@ -462,6 +466,10 @@ class SoundTouchDevice(MediaPlayerEntity): return attributes + async def async_browse_media(self, media_content_type=None, media_content_id=None): + """Implement the websocket media browsing helper.""" + return await media_source.async_browse_media(self.hass, media_content_id) + def get_zone_info(self): """Return the current zone info.""" zone_status = self._device.zone_status() diff --git a/homeassistant/components/spc/alarm_control_panel.py b/homeassistant/components/spc/alarm_control_panel.py index 4fe1bd4d5d7..d519e6b7f2b 100644 --- a/homeassistant/components/spc/alarm_control_panel.py +++ b/homeassistant/components/spc/alarm_control_panel.py @@ -4,11 +4,7 @@ from __future__ import annotations from pyspcwebgw.const import AreaMode import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, @@ -55,6 +51,12 @@ async def async_setup_platform( class SpcAlarm(alarm.AlarmControlPanelEntity): """Representation of the SPC alarm panel.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + ) + def __init__(self, area, api): """Initialize the SPC alarm panel.""" self._area = area @@ -95,11 +97,6 @@ class SpcAlarm(alarm.AlarmControlPanelEntity): """Return the state of the device.""" return _get_alarm_state(self._area) - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - async def async_alarm_disarm(self, code=None): """Send disarm command.""" diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py index 40b9f377b31..e80ec77592b 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -1,12 +1,6 @@ """Support for Spider thermostats.""" from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -16,9 +10,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN HA_STATE_TO_SPIDER = { - HVAC_MODE_COOL: "Cool", - HVAC_MODE_HEAT: "Heat", - HVAC_MODE_OFF: "Idle", + HVACMode.COOL: "Cool", + HVACMode.HEAT: "Heat", + HVACMode.OFF: "Idle", } SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()} @@ -66,8 +60,10 @@ class SpiderThermostat(ClimateEntity): def supported_features(self): """Return the list of supported features.""" if self.thermostat.has_fan_mode: - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - return SUPPORT_TARGET_TEMPERATURE + return ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) + return ClimateEntityFeature.TARGET_TEMPERATURE @property def unique_id(self): @@ -110,12 +106,12 @@ class SpiderThermostat(ClimateEntity): return self.thermostat.maximum_temperature @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" return SPIDER_STATE_TO_HA[self.thermostat.operation_mode] @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available operation modes.""" return self.support_hvac @@ -126,7 +122,7 @@ class SpiderThermostat(ClimateEntity): self.thermostat.set_temperature(temperature) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" self.thermostat.set_operation_mode(HA_STATE_TO_SPIDER.get(hvac_mode)) diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index 9dcf1fee6dc..dd18a05bdc7 100644 --- a/homeassistant/components/spotify/manifest.json +++ b/homeassistant/components/spotify/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/spotify", "requirements": ["spotipy==2.19.0"], "zeroconf": ["_spotify-connect._tcp.local."], - "dependencies": ["http"], + "dependencies": ["auth"], "codeowners": ["@frenck"], "config_flow": true, "quality_scale": "silver", diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 3ed7be99041..7d24f1deee8 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -10,7 +10,11 @@ import requests from spotipy import SpotifyException from yarl import URL -from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_EPISODE, MEDIA_TYPE_MUSIC, @@ -19,17 +23,6 @@ from homeassistant.components.media_player.const import ( REPEAT_MODE_ALL, REPEAT_MODE_OFF, REPEAT_MODE_ONE, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_VOLUME_SET, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID, STATE_IDLE, STATE_PAUSED, STATE_PLAYING @@ -50,17 +43,17 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(minutes=1) SUPPORT_SPOTIFY = ( - SUPPORT_BROWSE_MEDIA - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_REPEAT_SET - | SUPPORT_SEEK - | SUPPORT_SELECT_SOURCE - | SUPPORT_SHUFFLE_SET - | SUPPORT_VOLUME_SET + MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.VOLUME_SET ) REPEAT_MODE_MAPPING_TO_HA = { diff --git a/homeassistant/components/spotify/translations/hu.json b/homeassistant/components/spotify/translations/hu.json index 136e6185b46..f387ff6bb3a 100644 --- a/homeassistant/components/spotify/translations/hu.json +++ b/homeassistant/components/spotify/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A Spotify integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "reauth_account_mismatch": "A Spotify-fi\u00f3kkal hiteles\u00edtett fi\u00f3k nem egyezik meg az \u00faj hiteles\u00edt\u00e9shez sz\u00fcks\u00e9ges fi\u00f3kkal." }, "create_entry": { @@ -11,7 +11,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "reauth_confirm": { "description": "A Spotify integr\u00e1ci\u00f3nak \u00fajra hiteles\u00edtenie kell a Spotify fi\u00f3kot: {account}", diff --git a/homeassistant/components/spotify/translations/it.json b/homeassistant/components/spotify/translations/it.json index d40fa60b4d4..445b7a233e4 100644 --- a/homeassistant/components/spotify/translations/it.json +++ b/homeassistant/components/spotify/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Tempo scaduto nella generazione dell'URL di autorizzazione.", - "missing_configuration": "L'integrazione di Spotify non \u00e8 configurata. Si prega di seguire la documentazione.", + "missing_configuration": "L'integrazione di Spotify non \u00e8 configurata. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "reauth_account_mismatch": "L'account Spotify con cui sei autenticato non corrisponde all'account necessario per la nuova autenticazione." }, diff --git a/homeassistant/components/sql/__init__.py b/homeassistant/components/sql/__init__.py index ae354f85adb..3c83b01b284 100644 --- a/homeassistant/components/sql/__init__.py +++ b/homeassistant/components/sql/__init__.py @@ -1 +1,21 @@ """The sql component.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import PLATFORMS + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up SQL from a config entry.""" + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload SQL config entry.""" + + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/sql/config_flow.py b/homeassistant/components/sql/config_flow.py new file mode 100644 index 00000000000..9150cb8f63d --- /dev/null +++ b/homeassistant/components/sql/config_flow.py @@ -0,0 +1,209 @@ +"""Adds config flow for SQL integration.""" +from __future__ import annotations + +import logging +from typing import Any + +import sqlalchemy +from sqlalchemy.engine import Result +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import scoped_session, sessionmaker +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.recorder import CONF_DB_URL, DEFAULT_DB_FILE, DEFAULT_URL +from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import selector + +from .const import CONF_COLUMN_NAME, CONF_QUERY, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME, default="Select SQL Query"): selector.TextSelector(), + vol.Optional(CONF_DB_URL): selector.TextSelector(), + vol.Required(CONF_COLUMN_NAME): selector.TextSelector(), + vol.Required(CONF_QUERY): selector.TextSelector(), + vol.Optional(CONF_UNIT_OF_MEASUREMENT): selector.TextSelector(), + vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(), + } +) + + +def validate_sql_select(value: str) -> str | None: + """Validate that value is a SQL SELECT query.""" + if not value.lstrip().lower().startswith("select"): + raise ValueError("Incorrect Query") + return value + + +def validate_query(db_url: str, query: str, column: str) -> bool: + """Validate SQL query.""" + try: + engine = sqlalchemy.create_engine(db_url) + sessmaker = scoped_session(sessionmaker(bind=engine)) + except SQLAlchemyError as error: + raise error + + sess: scoped_session = sessmaker() + + try: + result: Result = sess.execute(query) + for res in result.mappings(): + data = res[column] + _LOGGER.debug("Return value from query: %s", data) + except SQLAlchemyError as error: + if sess: + sess.close() + raise ValueError(error) from error + + if sess: + sess.close() + + return True + + +class SQLConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for SQL integration.""" + + VERSION = 1 + + entry: config_entries.ConfigEntry + hass: HomeAssistant + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> SQLOptionsFlowHandler: + """Get the options flow for this handler.""" + return SQLOptionsFlowHandler(config_entry) + + async def async_step_import(self, config: dict[str, Any] | None) -> FlowResult: + """Import a configuration from config.yaml.""" + + self._async_abort_entries_match(config) + return await self.async_step_user(user_input=config) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the user step.""" + errors = {} + db_url_default = DEFAULT_URL.format( + hass_config_path=self.hass.config.path(DEFAULT_DB_FILE) + ) + + if user_input is not None: + + db_url = user_input.get(CONF_DB_URL, db_url_default) + query = user_input[CONF_QUERY] + column = user_input[CONF_COLUMN_NAME] + uom = user_input.get(CONF_UNIT_OF_MEASUREMENT) + value_template = user_input.get(CONF_VALUE_TEMPLATE) + name = user_input[CONF_NAME] + + try: + validate_sql_select(query) + await self.hass.async_add_executor_job( + validate_query, db_url, query, column + ) + except SQLAlchemyError: + errors["db_url"] = "db_url_invalid" + except ValueError: + errors["query"] = "query_invalid" + + if not errors: + return self.async_create_entry( + title=name, + data={}, + options={ + CONF_DB_URL: db_url, + CONF_QUERY: query, + CONF_COLUMN_NAME: column, + CONF_UNIT_OF_MEASUREMENT: uom, + CONF_VALUE_TEMPLATE: value_template, + CONF_NAME: name, + }, + ) + + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors=errors, + ) + + +class SQLOptionsFlowHandler(config_entries.OptionsFlow): + """Handle SQL options.""" + + def __init__(self, entry: config_entries.ConfigEntry) -> None: + """Initialize SQL options flow.""" + self.entry = entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage SQL options.""" + errors = {} + + if user_input is not None: + db_url = user_input[CONF_DB_URL] + query = user_input[CONF_QUERY] + column = user_input[CONF_COLUMN_NAME] + + try: + validate_sql_select(query) + await self.hass.async_add_executor_job( + validate_query, db_url, query, column + ) + except SQLAlchemyError: + errors["db_url"] = "db_url_invalid" + except ValueError: + errors["query"] = "query_invalid" + else: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + CONF_DB_URL, + description={ + "suggested_value": self.entry.options[CONF_DB_URL] + }, + ): selector.TextSelector(), + vol.Required( + CONF_QUERY, + description={"suggested_value": self.entry.options[CONF_QUERY]}, + ): selector.TextSelector(), + vol.Required( + CONF_COLUMN_NAME, + description={ + "suggested_value": self.entry.options[CONF_COLUMN_NAME] + }, + ): selector.TextSelector(), + vol.Optional( + CONF_UNIT_OF_MEASUREMENT, + description={ + "suggested_value": self.entry.options.get( + CONF_UNIT_OF_MEASUREMENT + ) + }, + ): selector.TextSelector(), + vol.Optional( + CONF_VALUE_TEMPLATE, + description={ + "suggested_value": self.entry.options.get( + CONF_VALUE_TEMPLATE + ) + }, + ): selector.TemplateSelector(), + } + ), + errors=errors, + ) diff --git a/homeassistant/components/sql/const.py b/homeassistant/components/sql/const.py new file mode 100644 index 00000000000..7dfcc3fba81 --- /dev/null +++ b/homeassistant/components/sql/const.py @@ -0,0 +1,12 @@ +"""Adds constants for SQL integration.""" +import re + +from homeassistant.const import Platform + +DOMAIN = "sql" +PLATFORMS = [Platform.SENSOR] + +CONF_COLUMN_NAME = "column" +CONF_QUERIES = "queries" +CONF_QUERY = "query" +DB_URL_RE = re.compile("//.*:.*@") diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index d8ced1625ad..c779e4567cd 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -2,7 +2,8 @@ "domain": "sql", "name": "SQL", "documentation": "https://www.home-assistant.io/integrations/sql", - "requirements": ["sqlalchemy==1.4.32"], - "codeowners": ["@dgomes"], + "requirements": ["sqlalchemy==1.4.36"], + "codeowners": ["@dgomes", "@gjohansson-ST"], + "config_flow": true, "iot_class": "local_polling" } diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index 0a240469f83..5e748bed55e 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -4,9 +4,9 @@ from __future__ import annotations from datetime import date import decimal import logging -import re import sqlalchemy +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import scoped_session, sessionmaker import voluptuous as vol @@ -15,41 +15,34 @@ from homeassistant.components.sensor import ( PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, SensorEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE from homeassistant.core import HomeAssistant +from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from .const import CONF_COLUMN_NAME, CONF_QUERIES, CONF_QUERY, DB_URL_RE, DOMAIN + _LOGGER = logging.getLogger(__name__) -CONF_COLUMN_NAME = "column" -CONF_QUERIES = "queries" -CONF_QUERY = "query" - -DB_URL_RE = re.compile("//.*:.*@") - def redact_credentials(data: str) -> str: """Redact credentials from string data.""" return DB_URL_RE.sub("//****:****@", data) -def validate_sql_select(value: str) -> str: - """Validate that value is a SQL SELECT query.""" - if not value.lstrip().lower().startswith("select"): - raise vol.Invalid("Only SELECT queries allowed") - return value - - _QUERY_SCHEME = vol.Schema( { vol.Required(CONF_COLUMN_NAME): cv.string, vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_QUERY): vol.All(cv.string, validate_sql_select), + vol.Required(CONF_QUERY): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_VALUE_TEMPLATE): cv.string, } ) @@ -58,67 +51,101 @@ PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( ) -def setup_platform( +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the SQL sensor platform.""" - if not (db_url := config.get(CONF_DB_URL)): - db_url = DEFAULT_URL.format(hass_config_path=hass.config.path(DEFAULT_DB_FILE)) + _LOGGER.warning( + # SQL config flow added in 2022.4 and should be removed in 2022.6 + "Configuration of the SQL sensor platform in YAML is deprecated and " + "will be removed in Home Assistant 2022.6; Your existing configuration " + "has been imported into the UI automatically and can be safely removed " + "from your configuration.yaml file" + ) - sess: scoped_session | None = None - try: - engine = sqlalchemy.create_engine(db_url) - sessmaker = scoped_session(sessionmaker(bind=engine)) - - # Run a dummy query just to test the db_url - sess = sessmaker() - sess.execute("SELECT 1;") - - except sqlalchemy.exc.SQLAlchemyError as err: - _LOGGER.error( - "Couldn't connect using %s DB_URL: %s", - redact_credentials(db_url), - redact_credentials(str(err)), - ) - return - finally: - if sess: - sess.close() - - queries = [] + default_db_url = DEFAULT_URL.format( + hass_config_path=hass.config.path(DEFAULT_DB_FILE) + ) for query in config[CONF_QUERIES]: - name: str = query[CONF_NAME] - query_str: str = query[CONF_QUERY] - unit: str | None = query.get(CONF_UNIT_OF_MEASUREMENT) - value_template: Template | None = query.get(CONF_VALUE_TEMPLATE) - column_name: str = query[CONF_COLUMN_NAME] + new_config = { + CONF_DB_URL: config.get(CONF_DB_URL, default_db_url), + CONF_NAME: query.get(CONF_NAME), + CONF_QUERY: query.get(CONF_QUERY), + CONF_UNIT_OF_MEASUREMENT: query.get(CONF_UNIT_OF_MEASUREMENT), + CONF_VALUE_TEMPLATE: query.get(CONF_VALUE_TEMPLATE), + CONF_COLUMN_NAME: query.get(CONF_COLUMN_NAME), + } + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=new_config, + ) + ) + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the SQL sensor entry.""" + + db_url: str = entry.options[CONF_DB_URL] + name: str = entry.options[CONF_NAME] + query_str: str = entry.options[CONF_QUERY] + unit: str | None = entry.options.get(CONF_UNIT_OF_MEASUREMENT) + template: str | None = entry.options.get(CONF_VALUE_TEMPLATE) + column_name: str = entry.options[CONF_COLUMN_NAME] + + value_template: Template | None = None + if template is not None: + try: + value_template = Template(template) + value_template.ensure_valid() + except TemplateError: + value_template = None if value_template is not None: value_template.hass = hass - # MSSQL uses TOP and not LIMIT - if not ("LIMIT" in query_str.upper() or "SELECT TOP" in query_str.upper()): - query_str = ( - query_str.replace("SELECT", "SELECT TOP 1") - if "mssql" in db_url - else query_str.replace(";", " LIMIT 1;") - ) + try: + engine = sqlalchemy.create_engine(db_url) + sessmaker = scoped_session(sessionmaker(bind=engine)) + except SQLAlchemyError as err: + _LOGGER.error("Can not open database %s", {redact_credentials(str(err))}) + return - sensor = SQLSensor( - name, sessmaker, query_str, column_name, unit, value_template + # MSSQL uses TOP and not LIMIT + if not ("LIMIT" in query_str.upper() or "SELECT TOP" in query_str.upper()): + query_str = ( + query_str.replace("SELECT", "SELECT TOP 1") + if "mssql" in db_url + else query_str.replace(";", " LIMIT 1;") ) - queries.append(sensor) - add_entities(queries, True) + async_add_entities( + [ + SQLSensor( + name, + sessmaker, + query_str, + column_name, + unit, + value_template, + entry.entry_id, + ) + ], + True, + ) class SQLSensor(SensorEntity): """Representation of an SQL sensor.""" + _attr_icon = "mdi:database-search" + def __init__( self, name: str, @@ -127,6 +154,7 @@ class SQLSensor(SensorEntity): column: str, unit: str | None, value_template: Template | None, + entry_id: str, ) -> None: """Initialize the SQL sensor.""" self._attr_name = name @@ -136,6 +164,13 @@ class SQLSensor(SensorEntity): self._column_name = column self.sessionmaker = sessmaker self._attr_extra_state_attributes = {} + self._attr_unique_id = entry_id + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, entry_id)}, + manufacturer="SQL", + name=name, + ) def update(self) -> None: """Retrieve sensor data from the query.""" @@ -145,7 +180,7 @@ class SQLSensor(SensorEntity): sess: scoped_session = self.sessionmaker() try: result = sess.execute(self._query) - except sqlalchemy.exc.SQLAlchemyError as err: + except SQLAlchemyError as err: _LOGGER.error( "Error executing query %s: %s", self._query, diff --git a/homeassistant/components/sql/strings.json b/homeassistant/components/sql/strings.json new file mode 100644 index 00000000000..8d3a194ac3e --- /dev/null +++ b/homeassistant/components/sql/strings.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, + "error": { + "db_url_invalid": "Database URL invalid", + "query_invalid": "SQL Query invalid", + "value_template_invalid": "Value Template invalid" + }, + "step": { + "user": { + "data": { + "db_url": "Database URL", + "name": "[%key:common::config_flow::data::name%]", + "query": "Select Query", + "column": "Column", + "unit_of_measurement": "Unit of Measure", + "value_template": "Value Template" + }, + "data_description": { + "db_url": "Database URL, leave empty to use default HA database", + "name": "Name that will be used for Config Entry and also the Sensor", + "query": "Query to run, needs to start with 'SELECT'", + "column": "Column for returned query to present as state", + "unit_of_measurement": "Unit of Measure (optional)", + "value_template": "Value Template (optional)" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "db_url": "[%key:component::sql::config::step::user::data::db_url%]", + "name": "[%key:component::sql::config::step::user::data::name%]", + "query": "[%key:component::sql::config::step::user::data::query%]", + "column": "[%key:component::sql::config::step::user::data::column%]", + "unit_of_measurement": "[%key:component::sql::config::step::user::data::unit_of_measurement%]", + "value_template": "[%key:component::sql::config::step::user::data::value_template%]" + }, + "data_description": { + "db_url": "[%key:component::sql::config::step::user::data_description::db_url%]", + "name": "[%key:component::sql::config::step::user::data_description::name%]", + "query": "[%key:component::sql::config::step::user::data_description::query%]", + "column": "[%key:component::sql::config::step::user::data_description::column%]", + "unit_of_measurement": "[%key:component::sql::config::step::user::data_description::unit_of_measurement%]", + "value_template": "[%key:component::sql::config::step::user::data_description::value_template%]" + } + } + }, + "error": { + "db_url_invalid": "[%key:component::sql::config::error::db_url_invalid%]", + "query_invalid": "[%key:component::sql::config::error::query_invalid%]", + "value_template_invalid": "[%key:component::sql::config::error::value_template_invalid%]" + } + } +} diff --git a/homeassistant/components/sql/translations/ca.json b/homeassistant/components/sql/translations/ca.json new file mode 100644 index 00000000000..d69f22ca855 --- /dev/null +++ b/homeassistant/components/sql/translations/ca.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja est\u00e0 configurat" + }, + "error": { + "db_url_invalid": "URL de la base de dades inv\u00e0lid", + "query_invalid": "Consulta SQL inv\u00e0lida", + "value_template_invalid": "Plantilla de valor inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "column": "Columna", + "db_url": "URL de la base de dades", + "query": "Selecciona la consulta", + "unit_of_measurement": "Unitat de mesura", + "value_template": "Plantilla de valor" + }, + "data_description": { + "column": "Columna de resposta de la consulta per presentar com a estat", + "db_url": "URL de la base de dades, deixa-ho en blanc per utilitzar la predeterminada de HA", + "query": "Consulta a executar, ha de comen\u00e7ar per 'SELECT'", + "unit_of_measurement": "Unitat de mesura (opcional)", + "value_template": "Plantilla de valor (opcional)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "URL de la base de dades inv\u00e0lid", + "query_invalid": "Consulta SQL inv\u00e0lida", + "value_template_invalid": "Plantilla de valor inv\u00e0lida" + }, + "step": { + "init": { + "data": { + "column": "Columna", + "db_url": "URL de la base de dades", + "query": "Selecciona la consulta", + "unit_of_measurement": "Unitat de mesura", + "value_template": "Plantilla de valor" + }, + "data_description": { + "column": "Columna de resposta de la consulta per presentar com a estat", + "db_url": "URL de la base de dades, deixa-ho en blanc per utilitzar la predeterminada de HA", + "query": "Consulta a executar, ha de comen\u00e7ar per 'SELECT'", + "unit_of_measurement": "Unitat de mesura (opcional)", + "value_template": "Plantilla de valor (opcional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/de.json b/homeassistant/components/sql/translations/de.json new file mode 100644 index 00000000000..2207997133d --- /dev/null +++ b/homeassistant/components/sql/translations/de.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "db_url_invalid": "Datenbank-URL ung\u00fcltig", + "query_invalid": "SQL-Abfrage ung\u00fcltig", + "value_template_invalid": "Wertvorlage ung\u00fcltig" + }, + "step": { + "user": { + "data": { + "column": "Spalte", + "db_url": "Datenbank-URL", + "query": "Abfrage ausw\u00e4hlen", + "unit_of_measurement": "Ma\u00dfeinheit", + "value_template": "Wertvorlage" + }, + "data_description": { + "column": "Spalte f\u00fcr die zur\u00fcckgegebene Abfrage, die als Status angezeigt werden soll", + "db_url": "Datenbank-URL, leer lassen, um die Standard-HA-Datenbank zu verwenden", + "query": "Auszuf\u00fchrende Abfrage, muss mit 'SELECT' beginnen", + "unit_of_measurement": "Ma\u00dfeinheit (optional)", + "value_template": "Wertvorlage (optional)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Datenbank-URL ung\u00fcltig", + "query_invalid": "SQL-Abfrage ung\u00fcltig", + "value_template_invalid": "Wertvorlage ung\u00fcltig" + }, + "step": { + "init": { + "data": { + "column": "Spalte", + "db_url": "Datenbank-URL", + "query": "Abfrage ausw\u00e4hlen", + "unit_of_measurement": "Ma\u00dfeinheit", + "value_template": "Wertvorlage" + }, + "data_description": { + "column": "Spalte f\u00fcr die zur\u00fcckgegebene Abfrage, die als Status angezeigt werden soll", + "db_url": "Datenbank-URL, leer lassen, um die Standard-HA-Datenbank zu verwenden", + "query": "Auszuf\u00fchrende Abfrage, muss mit 'SELECT' beginnen", + "unit_of_measurement": "Ma\u00dfeinheit (optional)", + "value_template": "Wertvorlage (optional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/el.json b/homeassistant/components/sql/translations/el.json new file mode 100644 index 00000000000..4f19401a8f3 --- /dev/null +++ b/homeassistant/components/sql/translations/el.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "db_url_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd", + "query_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 SQL", + "value_template_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03c0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03b9\u03bc\u03ae\u03c2" + }, + "step": { + "user": { + "data": { + "column": "\u03a3\u03c4\u03ae\u03bb\u03b7", + "db_url": "URL \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd", + "query": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03c1\u03c9\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", + "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2", + "value_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf \u03b1\u03be\u03af\u03b1\u03c2" + }, + "data_description": { + "column": "\u03a3\u03c4\u03ae\u03bb\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03b5\u03c6\u03cc\u03bc\u03b5\u03bd\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03c9\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", + "db_url": "URL \u03c4\u03b7\u03c2 \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b2\u03ac\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd HA", + "query": "\u03a4\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03bc\u03b5 'SELECT'", + "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "value_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03b9\u03bc\u03ae\u03c2 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd", + "query_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 SQL", + "value_template_invalid": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03c0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03b9\u03bc\u03ae\u03c2" + }, + "step": { + "init": { + "data": { + "column": "\u03a3\u03c4\u03ae\u03bb\u03b7", + "db_url": "URL \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd", + "query": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03b5\u03c1\u03c9\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", + "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2", + "value_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03b9\u03bc\u03ae\u03c2" + }, + "data_description": { + "column": "\u03a3\u03c4\u03ae\u03bb\u03b7 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03b5\u03c6\u03cc\u03bc\u03b5\u03bd\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03b1\u03c3\u03c4\u03b5\u03af \u03c9\u03c2 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", + "db_url": "URL \u03c4\u03b7\u03c2 \u03b2\u03ac\u03c3\u03b7\u03c2 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03b5\u03bd\u03cc \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b2\u03ac\u03c3\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03c9\u03bd HA", + "query": "\u03a4\u03bf \u03b5\u03c1\u03ce\u03c4\u03b7\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03bc\u03b5 'SELECT'", + "unit_of_measurement": "\u039c\u03bf\u03bd\u03ac\u03b4\u03b1 \u03bc\u03ad\u03c4\u03c1\u03b7\u03c3\u03b7\u03c2 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "value_template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf \u03c4\u03b9\u03bc\u03ae\u03c2 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/en.json b/homeassistant/components/sql/translations/en.json new file mode 100644 index 00000000000..4b72d024df4 --- /dev/null +++ b/homeassistant/components/sql/translations/en.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "db_url_invalid": "Database URL invalid", + "query_invalid": "SQL Query invalid", + "value_template_invalid": "Value Template invalid" + }, + "step": { + "user": { + "data": { + "column": "Column", + "db_url": "Database URL", + "name": "Name", + "query": "Select Query", + "unit_of_measurement": "Unit of Measure", + "value_template": "Value Template" + }, + "data_description": { + "column": "Column for returned query to present as state", + "db_url": "Database URL, leave empty to use default HA database", + "name": "Name that will be used for Config Entry and also the Sensor", + "query": "Query to run, needs to start with 'SELECT'", + "unit_of_measurement": "Unit of Measure (optional)", + "value_template": "Value Template (optional)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Database URL invalid", + "query_invalid": "SQL Query invalid", + "value_template_invalid": "Value Template invalid" + }, + "step": { + "init": { + "data": { + "column": "Column", + "db_url": "Database URL", + "name": "Name", + "query": "Select Query", + "unit_of_measurement": "Unit of Measure", + "value_template": "Value Template" + }, + "data_description": { + "column": "Column for returned query to present as state", + "db_url": "Database URL, leave empty to use default HA database", + "name": "Name that will be used for Config Entry and also the Sensor", + "query": "Query to run, needs to start with 'SELECT'", + "unit_of_measurement": "Unit of Measure (optional)", + "value_template": "Value Template (optional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/et.json b/homeassistant/components/sql/translations/et.json new file mode 100644 index 00000000000..b1f7339aba2 --- /dev/null +++ b/homeassistant/components/sql/translations/et.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud" + }, + "error": { + "db_url_invalid": "Andmebaasi URL on vigane", + "query_invalid": "SQL-p\u00e4ring on kehtetu", + "value_template_invalid": "V\u00e4\u00e4rtuse mall on kehtetu" + }, + "step": { + "user": { + "data": { + "column": "Veerg", + "db_url": "Andmebaasi URL", + "query": "Vali p\u00e4ring", + "unit_of_measurement": "M\u00f5\u00f5t\u00fchik", + "value_template": "V\u00e4\u00e4rtuse mall" + }, + "data_description": { + "column": "Tagastatud p\u00e4ringu veerg olekuna esitamiseks", + "db_url": "Andmebaasi URL, j\u00e4ta t\u00fchjaks, et kasutada HA vaikeandmebaasi", + "query": "K\u00e4ivitatav p\u00e4ring peab algama 'SELECT'-iga.", + "unit_of_measurement": "M\u00f5\u00f5t\u00fchik (valikuline)", + "value_template": "V\u00e4\u00e4rtuse mall (valikuline)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Andmebaasi URL on vigane", + "query_invalid": "V\u00e4\u00e4rtuse mall on kehtetu", + "value_template_invalid": "V\u00e4\u00e4rtuse mall on kehtetu" + }, + "step": { + "init": { + "data": { + "column": "Veerg", + "db_url": "Andmebaasi URL", + "query": "Vali p\u00e4ring", + "unit_of_measurement": "M\u00f5\u00f5t\u00fchik", + "value_template": "Tagastatud p\u00e4ringu veerg olekuna esitamiseks" + }, + "data_description": { + "column": "Tagastatud p\u00e4ringu veerg olekuna esitamiseks", + "db_url": "Andmebaasi URL, j\u00e4ta t\u00fchjaks, et kasutada HA vaikeandmebaasi", + "query": "K\u00e4ivitatav p\u00e4ring peab algama 'SELECT'-iga.", + "unit_of_measurement": "M\u00f5\u00f5t\u00fchik (valikuline)", + "value_template": "V\u00e4\u00e4rtuse mall (valikuline)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/fr.json b/homeassistant/components/sql/translations/fr.json new file mode 100644 index 00000000000..078fdc44d24 --- /dev/null +++ b/homeassistant/components/sql/translations/fr.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "db_url_invalid": "URL de la base de donn\u00e9es non valide", + "query_invalid": "Requ\u00eate SQL non valide", + "value_template_invalid": "Mod\u00e8le de valeur non valide" + }, + "step": { + "user": { + "data": { + "column": "Colonne", + "db_url": "URL de la base de donn\u00e9es", + "query": "Requ\u00eate de s\u00e9lection", + "unit_of_measurement": "Unit\u00e9 de mesure", + "value_template": "Mod\u00e8le de valeur" + }, + "data_description": { + "column": "La colonne de la r\u00e9ponse \u00e0 la requ\u00eate \u00e0 pr\u00e9senter en tant qu'\u00e9tat", + "db_url": "L'URL de la base de donn\u00e9es, laisser vide pour utiliser la base de donn\u00e9es HA par d\u00e9faut", + "query": "La requ\u00eate \u00e0 ex\u00e9cuter, doit commencer par \u00ab\u00a0SELECT\u00a0\u00bb", + "unit_of_measurement": "Unit\u00e9 de mesure (facultatif)", + "value_template": "Mod\u00e8le de valeur (facultatif)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "URL de la base de donn\u00e9es non valide", + "query_invalid": "Requ\u00eate SQL non valide", + "value_template_invalid": "Mod\u00e8le de valeur non valide" + }, + "step": { + "init": { + "data": { + "column": "Colonne", + "db_url": "URL de la base de donn\u00e9es", + "query": "Requ\u00eate de s\u00e9lection", + "unit_of_measurement": "Unit\u00e9 de mesure", + "value_template": "Mod\u00e8le de valeur" + }, + "data_description": { + "column": "La colonne de la r\u00e9ponse \u00e0 la requ\u00eate \u00e0 pr\u00e9senter en tant qu'\u00e9tat", + "db_url": "L'URL de la base de donn\u00e9es, laisser vide pour utiliser la base de donn\u00e9es HA par d\u00e9faut", + "query": "La requ\u00eate \u00e0 ex\u00e9cuter, doit commencer par \u00ab\u00a0SELECT\u00a0\u00bb", + "unit_of_measurement": "Unit\u00e9 de mesure (facultatif)", + "value_template": "Mod\u00e8le de valeur (facultatif)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/hu.json b/homeassistant/components/sql/translations/hu.json new file mode 100644 index 00000000000..cf125215ae3 --- /dev/null +++ b/homeassistant/components/sql/translations/hu.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "db_url_invalid": "\u00c9rv\u00e9nytelen adatb\u00e1zis URL-c\u00edm", + "query_invalid": "\u00c9rv\u00e9nytelen SQL-lek\u00e9rdez\u00e9s", + "value_template_invalid": "\u00c9rv\u00e9nytelen \u00e9rt\u00e9ksablon" + }, + "step": { + "user": { + "data": { + "column": "Oszlop", + "db_url": "Adatb\u00e1zis URL", + "query": "Lek\u00e9rdez\u00e9s kijel\u00f6l\u00e9se", + "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g", + "value_template": "\u00c9rt\u00e9ksablon" + }, + "data_description": { + "column": "\u00c1llapotk\u00e9nt megjelen\u00edtend\u0151, lek\u00e9rdezett oszlop", + "db_url": "Adatb\u00e1zis URL-c\u00edme, hagyja \u00fcresen az alap\u00e9rtelmezett HA-adatb\u00e1zis haszn\u00e1lat\u00e1hoz", + "query": "A futtatand\u00f3 lek\u00e9rdez\u00e9snek 'SELECT'-el kell kezd\u0151dnie.", + "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g (nem k\u00f6telez\u0151)", + "value_template": "\u00c9rt\u00e9ksablon (nem k\u00f6telez\u0151)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "\u00c9rv\u00e9nytelen adatb\u00e1zis URL-c\u00edm", + "query_invalid": "\u00c9rv\u00e9nytelen SQL-lek\u00e9rdez\u00e9s", + "value_template_invalid": "\u00c9rv\u00e9nytelen \u00e9rt\u00e9ksablon" + }, + "step": { + "init": { + "data": { + "column": "Oszlop", + "db_url": "Adatb\u00e1zis URL", + "query": "Lek\u00e9rdez\u00e9s kijel\u00f6l\u00e9se", + "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g", + "value_template": "\u00c9rt\u00e9ksablon" + }, + "data_description": { + "column": "\u00c1llapotk\u00e9nt megjelen\u00edtend\u0151, lek\u00e9rdezett oszlop", + "db_url": "Adatb\u00e1zis URL-c\u00edme, hagyja \u00fcresen az alap\u00e9rtelmezett HA-adatb\u00e1zis haszn\u00e1lat\u00e1hoz", + "query": "A futtatand\u00f3 lek\u00e9rdez\u00e9snek 'SELECT'-el kell kezd\u0151dnie.", + "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g (nem k\u00f6telez\u0151)", + "value_template": "\u00c9rt\u00e9ksablon (nem k\u00f6telez\u0151)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/id.json b/homeassistant/components/sql/translations/id.json new file mode 100644 index 00000000000..9cd3574c6c9 --- /dev/null +++ b/homeassistant/components/sql/translations/id.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "db_url_invalid": "URL database tidak valid", + "query_invalid": "Kueri SQL tidak valid", + "value_template_invalid": "Templat Nilai tidak valid" + }, + "step": { + "user": { + "data": { + "column": "Kolom", + "db_url": "URL Database", + "query": "Kueri Select", + "unit_of_measurement": "Satuan Ukuran", + "value_template": "Templat Nilai" + }, + "data_description": { + "column": "Kolom pada kueri yang dikembalikan untuk ditampilkan sebagai status", + "db_url": "URL database, kosongkan untuk menggunakan database HA default", + "query": "Kueri untuk dijalankan, perlu dimulai dengan 'SELECT'", + "unit_of_measurement": "Satuan Ukuran (opsional)", + "value_template": "Template Nilai (opsional)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "URL database tidak valid", + "query_invalid": "Kueri SQL tidak valid", + "value_template_invalid": "Templat Nilai tidak valid" + }, + "step": { + "init": { + "data": { + "column": "Kolom", + "db_url": "URL Database", + "query": "Kueri Select", + "unit_of_measurement": "Satuan Ukuran", + "value_template": "Templat Nilai" + }, + "data_description": { + "column": "Kolom pada kueri yang dikembalikan untuk ditampilkan sebagai status", + "db_url": "URL database, kosongkan untuk menggunakan database HA default", + "query": "Kueri untuk dijalankan, perlu dimulai dengan 'SELECT'", + "unit_of_measurement": "Satuan Ukuran (opsional)", + "value_template": "Template Nilai (opsional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/it.json b/homeassistant/components/sql/translations/it.json new file mode 100644 index 00000000000..01672eb5a51 --- /dev/null +++ b/homeassistant/components/sql/translations/it.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato" + }, + "error": { + "db_url_invalid": "URL database non valido", + "query_invalid": "Query SQL non valida", + "value_template_invalid": "Modello di valore non valido" + }, + "step": { + "user": { + "data": { + "column": "Colonna", + "db_url": "URL del database", + "query": "Query Select", + "unit_of_measurement": "Unit\u00e0 di misura", + "value_template": "Modello di valore" + }, + "data_description": { + "column": "Colonna per la query restituita da presentare come stato", + "db_url": "URL del database, lascia vuoto per utilizzare il database predefinito di Home Assistant", + "query": "Query da eseguire, deve iniziare con 'SELECT'", + "unit_of_measurement": "Unit\u00e0 di misura (opzionale)", + "value_template": "Modello di valore (opzionale)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "URL database non valido", + "query_invalid": "Query SQL non valida", + "value_template_invalid": "Modello di valore non valido" + }, + "step": { + "init": { + "data": { + "column": "Colonna", + "db_url": "URL del database", + "query": "Query Select", + "unit_of_measurement": "Unit\u00e0 di misura", + "value_template": "Modello di valore" + }, + "data_description": { + "column": "Colonna per la query restituita da presentare come stato", + "db_url": "URL del database, lascia vuoto per utilizzare il database predefinito di Home Assistant", + "query": "Query da eseguire, deve iniziare con 'SELECT'", + "unit_of_measurement": "Unit\u00e0 di misura (opzionale)", + "value_template": "Modello di valore (opzionale)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/nl.json b/homeassistant/components/sql/translations/nl.json new file mode 100644 index 00000000000..ee6ad3503bb --- /dev/null +++ b/homeassistant/components/sql/translations/nl.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "db_url_invalid": "Database URL ongeldig", + "query_invalid": "SQL Query ongeldig", + "value_template_invalid": "Waarde Template ongeldig" + }, + "step": { + "user": { + "data": { + "column": "Kolom", + "db_url": "Database URL", + "query": "Selecteer Query", + "unit_of_measurement": "Meeteenheid", + "value_template": "Waarde Template" + }, + "data_description": { + "column": "Kolom voor teruggestuurde query om als status te presenteren", + "db_url": "Database URL, leeg laten om de standaard HA database te gebruiken", + "unit_of_measurement": "Meeteenheid (optioneel)", + "value_template": "Waarde Template (optioneel)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Database URL ongeldig", + "query_invalid": "SQL Query ongeldig", + "value_template_invalid": "Waarde Template ongeldig" + }, + "step": { + "init": { + "data": { + "column": "Kolom", + "db_url": "Database URL", + "query": "Selecteer Query", + "unit_of_measurement": "Meeteenheid", + "value_template": "Waarde Template" + }, + "data_description": { + "column": "Kolom voor teruggestuurde query om als status te presenteren", + "db_url": "Database URL, leeg laten om de standaard HA database te gebruiken", + "unit_of_measurement": "Meeteenheid (optioneel)", + "value_template": "Waarde Template (optioneel)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/no.json b/homeassistant/components/sql/translations/no.json new file mode 100644 index 00000000000..292486d3c0f --- /dev/null +++ b/homeassistant/components/sql/translations/no.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "db_url_invalid": "Database-URL er ugyldig", + "query_invalid": "SQL-sp\u00f8rringen er ugyldig", + "value_template_invalid": "Verdimalen er ugyldig" + }, + "step": { + "user": { + "data": { + "column": "Kolonne", + "db_url": "Database URL", + "query": "Velg Sp\u00f8rring", + "unit_of_measurement": "M\u00e5leenhet", + "value_template": "Verdimal" + }, + "data_description": { + "column": "Kolonne for returnert foresp\u00f8rsel for \u00e5 presentere som tilstand", + "db_url": "Database-URL, la st\u00e5 tom for \u00e5 bruke standard HA-database", + "query": "Sp\u00f8rsm\u00e5let skal kj\u00f8res, m\u00e5 starte med \"SELECT\"", + "unit_of_measurement": "M\u00e5leenhet (valgfritt)", + "value_template": "Verdimal (valgfritt)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Database-URL er ugyldig", + "query_invalid": "SQL-sp\u00f8rringen er ugyldig", + "value_template_invalid": "Verdimalen er ugyldig" + }, + "step": { + "init": { + "data": { + "column": "Kolonne", + "db_url": "Database URL", + "query": "Velg Sp\u00f8rring", + "unit_of_measurement": "M\u00e5leenhet", + "value_template": "Verdimal" + }, + "data_description": { + "column": "Kolonne for returnert foresp\u00f8rsel for \u00e5 presentere som tilstand", + "db_url": "Database-URL, la st\u00e5 tom for \u00e5 bruke standard HA-database", + "query": "Sp\u00f8rsm\u00e5let skal kj\u00f8res, m\u00e5 starte med \"SELECT\"", + "unit_of_measurement": "M\u00e5leenhet (valgfritt)", + "value_template": "Verdimal (valgfritt)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/pl.json b/homeassistant/components/sql/translations/pl.json new file mode 100644 index 00000000000..0e8cb47b148 --- /dev/null +++ b/homeassistant/components/sql/translations/pl.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane" + }, + "error": { + "db_url_invalid": "Nieprawid\u0142owy adres URL bazy danych", + "query_invalid": "Nieprawid\u0142owe zapytanie SQL", + "value_template_invalid": "Nieprawid\u0142owy szablon warto\u015bci" + }, + "step": { + "user": { + "data": { + "column": "Kolumna", + "db_url": "Adres URL bazy danych", + "query": "Wybierz zapytanie", + "unit_of_measurement": "Jednostka miary", + "value_template": "Szablon warto\u015bci" + }, + "data_description": { + "column": "Kolumna dla zwr\u00f3conego zapytania do przedstawienia jako stan", + "db_url": "Adres URL bazy danych, pozostaw puste, aby u\u017cy\u0107 domy\u015blnej bazy danych HA", + "query": "Zapytanie do uruchomienia, musi zaczyna\u0107 si\u0119 od \u201eSELECT\u201d", + "unit_of_measurement": "Jednostka miary (opcjonalnie)", + "value_template": "Szablon warto\u015bci (opcjonalnie)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Nieprawid\u0142owy adres URL bazy danych", + "query_invalid": "Nieprawid\u0142owe zapytanie SQL", + "value_template_invalid": "Nieprawid\u0142owy szablon warto\u015bci" + }, + "step": { + "init": { + "data": { + "column": "Kolumna", + "db_url": "Adres URL bazy danych", + "query": "Wybierz zapytanie", + "unit_of_measurement": "Jednostka miary", + "value_template": "Szablon warto\u015bci" + }, + "data_description": { + "column": "Kolumna dla zwr\u00f3conego zapytania do przedstawienia jako stan", + "db_url": "Adres URL bazy danych, pozostaw puste, aby u\u017cy\u0107 domy\u015blnej bazy danych HA", + "query": "Zapytanie do uruchomienia, musi zaczyna\u0107 si\u0119 od \u201eSELECT\u201d", + "unit_of_measurement": "Jednostka miary (opcjonalnie)", + "value_template": "Szablon warto\u015bci (opcjonalnie)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/pt-BR.json b/homeassistant/components/sql/translations/pt-BR.json new file mode 100644 index 00000000000..10df43c7e1f --- /dev/null +++ b/homeassistant/components/sql/translations/pt-BR.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada" + }, + "error": { + "db_url_invalid": "URL do banco de dados inv\u00e1lida", + "query_invalid": "Consulta SQL inv\u00e1lida", + "value_template_invalid": "Modelo do valor inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "column": "Coluna", + "db_url": "URL do banco de dados", + "query": "Selecionar consulta", + "unit_of_measurement": "Unidade de medida", + "value_template": "Modelo do valor" + }, + "data_description": { + "column": "Coluna para a consulta retornada para apresentar como estado", + "db_url": "URL do banco de dados, deixe em branco para usar o banco de dados padr\u00e3o", + "query": "Consulte para ser executada, precisa come\u00e7ar com 'SELECT'", + "unit_of_measurement": "Unidade de medida (opcional)", + "value_template": "Modelo do valor (opcional)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "URL do banco de dados inv\u00e1lida", + "query_invalid": "Consulta SQL inv\u00e1lida", + "value_template_invalid": "Modelo do valor inv\u00e1lido" + }, + "step": { + "init": { + "data": { + "column": "Coluna", + "db_url": "URL do banco de dados", + "query": "Selecionar consulta", + "unit_of_measurement": "Unidade de medida", + "value_template": "Modelo do valor" + }, + "data_description": { + "column": "Coluna para a consulta retornada para apresentar como estado", + "db_url": "URL do banco de dados, deixe em branco para usar o banco de dados padr\u00e3o", + "query": "Consulte para ser executada, precisa come\u00e7ar com 'SELECT'", + "unit_of_measurement": "Unidade de medida (opcional)", + "value_template": "Modelo do valor (opcional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/ru.json b/homeassistant/components/sql/translations/ru.json new file mode 100644 index 00000000000..8f8e0741583 --- /dev/null +++ b/homeassistant/components/sql/translations/ru.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "db_url_invalid": "\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445.", + "query_invalid": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 SQL-\u0437\u0430\u043f\u0440\u043e\u0441.", + "value_template_invalid": "\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f." + }, + "step": { + "user": { + "data": { + "column": "\u0421\u0442\u043e\u043b\u0431\u0435\u0446", + "db_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445", + "query": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u043f\u0440\u043e\u0441", + "unit_of_measurement": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f", + "value_template": "\u0428\u0430\u0431\u043b\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f" + }, + "data_description": { + "column": "\u0421\u0442\u043e\u043b\u0431\u0435\u0446 \u0434\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0432 \u0432\u0438\u0434\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f", + "db_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 HA \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", + "query": "\u0417\u0430\u043f\u0440\u043e\u0441 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f, \u0434\u043e\u043b\u0436\u0435\u043d \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c\u0441\u044f \u0441 'SELECT'", + "unit_of_measurement": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "value_template": "\u0428\u0430\u0431\u043b\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445.", + "query_invalid": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 SQL-\u0437\u0430\u043f\u0440\u043e\u0441.", + "value_template_invalid": "\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f." + }, + "step": { + "init": { + "data": { + "column": "\u0421\u0442\u043e\u043b\u0431\u0435\u0446", + "db_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445", + "query": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u043f\u0440\u043e\u0441", + "unit_of_measurement": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f", + "value_template": "\u0428\u0430\u0431\u043b\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f" + }, + "data_description": { + "column": "\u0421\u0442\u043e\u043b\u0431\u0435\u0446 \u0434\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0432 \u0432\u0438\u0434\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f", + "db_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u041e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 HA \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e", + "query": "\u0417\u0430\u043f\u0440\u043e\u0441 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f, \u0434\u043e\u043b\u0436\u0435\u043d \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c\u0441\u044f \u0441 'SELECT'", + "unit_of_measurement": "\u0415\u0434\u0438\u043d\u0438\u0446\u0430 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "value_template": "\u0428\u0430\u0431\u043b\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/tr.json b/homeassistant/components/sql/translations/tr.json new file mode 100644 index 00000000000..edbce58d65a --- /dev/null +++ b/homeassistant/components/sql/translations/tr.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "db_url_invalid": "Veritaban\u0131 URL'si ge\u00e7ersiz", + "query_invalid": "SQL Sorgusu ge\u00e7ersiz", + "value_template_invalid": "De\u011fer \u015eablonu ge\u00e7ersiz" + }, + "step": { + "user": { + "data": { + "column": "S\u00fctun", + "db_url": "Veritaban\u0131 URL'si", + "query": "Sorgu Se\u00e7", + "unit_of_measurement": "\u00d6l\u00e7\u00fc Birimi", + "value_template": "De\u011fer \u015eablonu" + }, + "data_description": { + "column": "D\u00f6nd\u00fcr\u00fclen sorgunun durum olarak sunulmas\u0131 i\u00e7in s\u00fctun", + "db_url": "Veritaban\u0131 URL'si, varsay\u0131lan HA veritaban\u0131n\u0131 kullanmak i\u00e7in bo\u015f b\u0131rak\u0131n", + "query": "\u00c7al\u0131\u015ft\u0131r\u0131lacak sorgu, 'SE\u00c7' ile ba\u015flamal\u0131d\u0131r", + "unit_of_measurement": "\u00d6l\u00e7\u00fc Birimi (iste\u011fe ba\u011fl\u0131)", + "value_template": "De\u011fer \u015eablonu (iste\u011fe ba\u011fl\u0131)" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "Veritaban\u0131 URL'si ge\u00e7ersiz", + "query_invalid": "SQL Sorgusu ge\u00e7ersiz", + "value_template_invalid": "De\u011fer \u015eablonu ge\u00e7ersiz" + }, + "step": { + "init": { + "data": { + "column": "S\u00fctun", + "db_url": "Veritaban\u0131 URL'si", + "query": "Sorgu Se\u00e7", + "unit_of_measurement": "\u00d6l\u00e7\u00fc Birimi", + "value_template": "De\u011fer \u015eablonu" + }, + "data_description": { + "column": "D\u00f6nd\u00fcr\u00fclen sorgunun durum olarak sunulmas\u0131 i\u00e7in s\u00fctun", + "db_url": "Veritaban\u0131 URL'si, varsay\u0131lan HA veritaban\u0131n\u0131 kullanmak i\u00e7in bo\u015f b\u0131rak\u0131n", + "query": "\u00c7al\u0131\u015ft\u0131r\u0131lacak sorgu, 'SE\u00c7' ile ba\u015flamal\u0131d\u0131r", + "unit_of_measurement": "\u00d6l\u00e7\u00fc Birimi (iste\u011fe ba\u011fl\u0131)", + "value_template": "De\u011fer \u015eablonu (iste\u011fe ba\u011fl\u0131)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sql/translations/zh-Hant.json b/homeassistant/components/sql/translations/zh-Hant.json new file mode 100644 index 00000000000..801d973049d --- /dev/null +++ b/homeassistant/components/sql/translations/zh-Hant.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "db_url_invalid": "\u8cc7\u6599\u5eab URL \u7121\u6548", + "query_invalid": "SQL \u67e5\u8a62\u7121\u6548", + "value_template_invalid": "\u6578\u503c\u6a21\u677f\u7121\u6548" + }, + "step": { + "user": { + "data": { + "column": "\u6b04\u4f4d", + "db_url": "\u8cc7\u6599\u5eab URL", + "query": "\u9078\u64c7\u67e5\u8a62", + "unit_of_measurement": "\u6e2c\u91cf\u55ae\u4f4d", + "value_template": "\u6578\u503c\u6a21\u677f" + }, + "data_description": { + "column": "\u67e5\u8a62\u56de\u8986\u6b04\u4f4d\u70ba\u72c0\u614b", + "db_url": "\u8cc7\u6599\u5eab URL\u3001\u4fdd\u7559\u7a7a\u767d\u4ee5\u4f7f\u7528\u9810\u8a2d HA \u8cc7\u6599\u5eab", + "query": "\u57f7\u884c\u7684\u67e5\u8a62\u3001\u9700\u8981\u4ee5 'SELECT' \u4f5c\u70ba\u958b\u982d", + "unit_of_measurement": "\u6e2c\u91cf\u55ae\u4f4d\uff08\u9078\u9805\uff09", + "value_template": "\u6578\u503c\u6a21\u677f\uff08\u9078\u9805\uff09" + } + } + } + }, + "options": { + "error": { + "db_url_invalid": "\u8cc7\u6599\u5eab URL \u7121\u6548", + "query_invalid": "SQL \u67e5\u8a62\u7121\u6548", + "value_template_invalid": "\u6578\u503c\u6a21\u677f\u7121\u6548" + }, + "step": { + "init": { + "data": { + "column": "\u6b04\u4f4d", + "db_url": "\u8cc7\u6599\u5eab URL", + "query": "\u9078\u64c7\u67e5\u8a62", + "unit_of_measurement": "\u6e2c\u91cf\u55ae\u4f4d", + "value_template": "\u6578\u503c\u6a21\u677f" + }, + "data_description": { + "column": "\u67e5\u8a62\u56de\u8986\u6b04\u4f4d\u70ba\u72c0\u614b", + "db_url": "\u8cc7\u6599\u5eab URL\u3001\u4fdd\u7559\u7a7a\u767d\u4ee5\u4f7f\u7528\u9810\u8a2d HA \u8cc7\u6599\u5eab", + "query": "\u57f7\u884c\u7684\u67e5\u8a62\u3001\u9700\u8981\u4ee5 'SELECT' \u4f5c\u70ba\u958b\u982d", + "unit_of_measurement": "\u6e2c\u91cf\u55ae\u4f4d\uff08\u9078\u9805\uff09", + "value_template": "\u6578\u503c\u6a21\u677f\uff08\u9078\u9805\uff09" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/manifest.json b/homeassistant/components/squeezebox/manifest.json index f36917f1a01..3f58ee1f275 100644 --- a/homeassistant/components/squeezebox/manifest.json +++ b/homeassistant/components/squeezebox/manifest.json @@ -1,6 +1,6 @@ { "domain": "squeezebox", - "name": "Logitech Squeezebox", + "name": "Squeezebox (Logitech Media Server)", "documentation": "https://www.home-assistant.io/integrations/squeezebox", "codeowners": ["@rajlaud"], "requirements": ["pysqueezebox==0.5.5"], diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 923af6c8c64..e302a75e0c2 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -9,7 +9,10 @@ from pysqueezebox import Server, async_discover import voluptuous as vol from homeassistant.components import media_source -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) @@ -17,20 +20,6 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, ) from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry from homeassistant.const import ( @@ -85,22 +74,6 @@ _LOGGER = logging.getLogger(__name__) DISCOVERY_INTERVAL = 60 -SUPPORT_SQUEEZEBOX = ( - SUPPORT_BROWSE_MEDIA - | SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SEEK - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_SHUFFLE_SET - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_STOP -) KNOWN_SERVERS = "known_servers" ATTR_PARAMETERS = "parameters" @@ -246,6 +219,23 @@ class SqueezeBoxEntity(MediaPlayerEntity): Wraps a pysqueezebox.Player() object. """ + _attr_supported_features = ( + MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.STOP + ) + def __init__(self, player): """Initialize the SqueezeBox device.""" self._player = player @@ -388,11 +378,6 @@ class SqueezeBoxEntity(MediaPlayerEntity): """Boolean if shuffle is enabled.""" return self._player.shuffle - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_SQUEEZEBOX - @property def sync_group(self): """List players we are synced with.""" diff --git a/homeassistant/components/srp_energy/translations/ca.json b/homeassistant/components/srp_energy/translations/ca.json index c6617e617d4..45a2d2fa943 100644 --- a/homeassistant/components/srp_energy/translations/ca.json +++ b/homeassistant/components/srp_energy/translations/ca.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/cs.json b/homeassistant/components/srp_energy/translations/cs.json index 74b4bd53090..74045d75b2e 100644 --- a/homeassistant/components/srp_energy/translations/cs.json +++ b/homeassistant/components/srp_energy/translations/cs.json @@ -18,6 +18,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/de.json b/homeassistant/components/srp_energy/translations/de.json index a7992cac9b1..d1975b8f59d 100644 --- a/homeassistant/components/srp_energy/translations/de.json +++ b/homeassistant/components/srp_energy/translations/de.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/el.json b/homeassistant/components/srp_energy/translations/el.json index 4583eb47823..99a5f1ea850 100644 --- a/homeassistant/components/srp_energy/translations/el.json +++ b/homeassistant/components/srp_energy/translations/el.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/en.json b/homeassistant/components/srp_energy/translations/en.json index 99926b18b4f..a031e56022e 100644 --- a/homeassistant/components/srp_energy/translations/en.json +++ b/homeassistant/components/srp_energy/translations/en.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/es.json b/homeassistant/components/srp_energy/translations/es.json index 849c5019d3b..ebd4583fe43 100644 --- a/homeassistant/components/srp_energy/translations/es.json +++ b/homeassistant/components/srp_energy/translations/es.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/et.json b/homeassistant/components/srp_energy/translations/et.json index 558bb4a19ed..52f3de847fb 100644 --- a/homeassistant/components/srp_energy/translations/et.json +++ b/homeassistant/components/srp_energy/translations/et.json @@ -19,6 +19,5 @@ } } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/fr.json b/homeassistant/components/srp_energy/translations/fr.json index b8544d4d469..93c6ade07c4 100644 --- a/homeassistant/components/srp_energy/translations/fr.json +++ b/homeassistant/components/srp_energy/translations/fr.json @@ -19,6 +19,5 @@ } } } - }, - "title": "\u00c9nergie SRP" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/hu.json b/homeassistant/components/srp_energy/translations/hu.json index 4d617e09cfc..b032f24e5fc 100644 --- a/homeassistant/components/srp_energy/translations/hu.json +++ b/homeassistant/components/srp_energy/translations/hu.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/id.json b/homeassistant/components/srp_energy/translations/id.json index fefcbff2ecb..113e07b987f 100644 --- a/homeassistant/components/srp_energy/translations/id.json +++ b/homeassistant/components/srp_energy/translations/id.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/it.json b/homeassistant/components/srp_energy/translations/it.json index d5ccf02d74c..f81b5e2e1a4 100644 --- a/homeassistant/components/srp_energy/translations/it.json +++ b/homeassistant/components/srp_energy/translations/it.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/ja.json b/homeassistant/components/srp_energy/translations/ja.json index 432c553910b..805a500502b 100644 --- a/homeassistant/components/srp_energy/translations/ja.json +++ b/homeassistant/components/srp_energy/translations/ja.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/ka.json b/homeassistant/components/srp_energy/translations/ka.json index f4e15ad5d9e..3aec1ed2f43 100644 --- a/homeassistant/components/srp_energy/translations/ka.json +++ b/homeassistant/components/srp_energy/translations/ka.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/ko.json b/homeassistant/components/srp_energy/translations/ko.json index b329cf1f2b1..bb0d912111e 100644 --- a/homeassistant/components/srp_energy/translations/ko.json +++ b/homeassistant/components/srp_energy/translations/ko.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json index 3cf2298b348..ce4ac90c223 100644 --- a/homeassistant/components/srp_energy/translations/nl.json +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/no.json b/homeassistant/components/srp_energy/translations/no.json index 5505e140cd3..51bb1c7fd18 100644 --- a/homeassistant/components/srp_energy/translations/no.json +++ b/homeassistant/components/srp_energy/translations/no.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/pl.json b/homeassistant/components/srp_energy/translations/pl.json index f89165a9065..eec873b6820 100644 --- a/homeassistant/components/srp_energy/translations/pl.json +++ b/homeassistant/components/srp_energy/translations/pl.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/pt-BR.json b/homeassistant/components/srp_energy/translations/pt-BR.json index b0dbfe9c6d0..e7613d468a3 100644 --- a/homeassistant/components/srp_energy/translations/pt-BR.json +++ b/homeassistant/components/srp_energy/translations/pt-BR.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/ru.json b/homeassistant/components/srp_energy/translations/ru.json index a492fa7dfb2..a94e18aa20a 100644 --- a/homeassistant/components/srp_energy/translations/ru.json +++ b/homeassistant/components/srp_energy/translations/ru.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/tr.json b/homeassistant/components/srp_energy/translations/tr.json index ead8238d82c..0c815f97186 100644 --- a/homeassistant/components/srp_energy/translations/tr.json +++ b/homeassistant/components/srp_energy/translations/tr.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Enerji" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/uk.json b/homeassistant/components/srp_energy/translations/uk.json index 144e40f15bb..2d4b399947f 100644 --- a/homeassistant/components/srp_energy/translations/uk.json +++ b/homeassistant/components/srp_energy/translations/uk.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/zh-Hans.json b/homeassistant/components/srp_energy/translations/zh-Hans.json index 36016f3e217..b0b26b02261 100644 --- a/homeassistant/components/srp_energy/translations/zh-Hans.json +++ b/homeassistant/components/srp_energy/translations/zh-Hans.json @@ -8,6 +8,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/zh-Hant.json b/homeassistant/components/srp_energy/translations/zh-Hant.json index adbf635100c..bed1cd5c3c1 100644 --- a/homeassistant/components/srp_energy/translations/zh-Hant.json +++ b/homeassistant/components/srp_energy/translations/zh-Hant.json @@ -19,6 +19,5 @@ } } } - }, - "title": "SRP Energy" + } } \ No newline at end of file diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index cdc5fe3242e..b59dc0ce1ee 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -11,12 +11,7 @@ import logging from typing import Any from async_upnp_client.aiohttp import AiohttpSessionRequester -from async_upnp_client.const import ( - AddressTupleVXType, - DeviceOrServiceType, - SsdpHeaders, - SsdpSource, -) +from async_upnp_client.const import AddressTupleVXType, DeviceOrServiceType, SsdpSource from async_upnp_client.description_cache import DescriptionCache from async_upnp_client.ssdp import SSDP_PORT, determine_source_target, is_ipv4_address from async_upnp_client.ssdp_listener import SsdpDevice, SsdpDeviceTracker, SsdpListener @@ -246,13 +241,13 @@ async def _async_process_callbacks( @core_callback def _async_headers_match( - headers: Mapping[str, Any], match_dict: dict[str, str] + headers: CaseInsensitiveDict, lower_match_dict: dict[str, str] ) -> bool: - for header, val in match_dict.items(): + for header, val in lower_match_dict.items(): if val == MATCH_ALL: if header not in headers: return False - elif headers.get(header) != val: + elif headers.get_lower(header) != val: return False return True @@ -328,7 +323,7 @@ class Scanner: @property def _all_headers_from_ssdp_devices( self, - ) -> dict[tuple[str, str], Mapping[str, Any]]: + ) -> dict[tuple[str, str], CaseInsensitiveDict]: return { (ssdp_device.udn, dst): headers for ssdp_device in self._ssdp_devices @@ -340,19 +335,21 @@ class Scanner: ) -> Callable[[], None]: """Register a callback.""" if match_dict is None: - match_dict = {} + lower_match_dict = {} + else: + lower_match_dict = {k.lower(): v for k, v in match_dict.items()} # Make sure any entries that happened # before the callback was registered are fired for headers in self._all_headers_from_ssdp_devices.values(): - if _async_headers_match(headers, match_dict): + if _async_headers_match(headers, lower_match_dict): await _async_process_callbacks( [callback], await self._async_headers_to_discovery_info(headers), SsdpChange.ALIVE, ) - callback_entry = (callback, match_dict) + callback_entry = (callback, lower_match_dict) self._callbacks.append(callback_entry) @core_callback @@ -461,13 +458,13 @@ class Scanner: @core_callback def _async_get_matching_callbacks( self, - combined_headers: SsdpHeaders, + combined_headers: CaseInsensitiveDict, ) -> list[SsdpCallback]: """Return a list of callbacks that match.""" return [ callback - for callback, match_dict in self._callbacks - if _async_headers_match(combined_headers, match_dict) + for callback, lower_match_dict in self._callbacks + if _async_headers_match(combined_headers, lower_match_dict) ] async def _ssdp_listener_callback( @@ -490,9 +487,8 @@ class Scanner: # If there are no changes from a search, do not trigger a config flow if source != SsdpSource.SEARCH_ALIVE: info_desc = await self._async_get_description_dict(location) or {} - assert isinstance(combined_headers, CaseInsensitiveDict) matching_domains = self.integration_matchers.async_matching_domains( - CaseInsensitiveDict({**combined_headers.as_dict(), **info_desc}) + CaseInsensitiveDict(combined_headers.as_dict(), **info_desc) ) if not callbacks and not matching_domains: @@ -530,7 +526,7 @@ class Scanner: return await self._description_cache.async_get_description_dict(location) or {} async def _async_headers_to_discovery_info( - self, headers: Mapping[str, Any] + self, headers: CaseInsensitiveDict ) -> SsdpServiceInfo: """Combine the headers and description into discovery_info. @@ -571,7 +567,7 @@ class Scanner: def discovery_info_from_headers_and_description( - combined_headers: Mapping[str, Any], + combined_headers: CaseInsensitiveDict, info_desc: Mapping[str, Any], ) -> SsdpServiceInfo: """Convert headers and description to discovery_info.""" diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index e55f16d9247..993a0033b1b 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["async-upnp-client==0.27.0"], + "requirements": ["async-upnp-client==0.29.0"], "dependencies": ["network"], "after_dependencies": ["zeroconf"], "codeowners": [], diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index ed2352657f4..bc6acb2732a 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -20,6 +20,7 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.const import ( + ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, CONF_ENTITY_ID, CONF_NAME, @@ -88,7 +89,7 @@ DEPRECATION_WARNING_CHARACTERISTIC = ( ) # Statistics supported by a sensor source (numeric) -STATS_NUMERIC_SUPPORT = ( +STATS_NUMERIC_SUPPORT = { STAT_AVERAGE_LINEAR, STAT_AVERAGE_STEP, STAT_AVERAGE_TIMELESS, @@ -110,26 +111,51 @@ STATS_NUMERIC_SUPPORT = ( STAT_VALUE_MAX, STAT_VALUE_MIN, STAT_VARIANCE, -) +} # Statistics supported by a binary_sensor source -STATS_BINARY_SUPPORT = ( +STATS_BINARY_SUPPORT = { STAT_AVERAGE_STEP, STAT_AVERAGE_TIMELESS, STAT_COUNT, STAT_MEAN, -) +} -STATS_NOT_A_NUMBER = ( +STATS_NOT_A_NUMBER = { STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, STAT_QUANTILES, -) +} -STATS_DATETIME = ( +STATS_DATETIME = { STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, -) +} + +# Statistics which retain the unit of the source entity +STAT_NUMERIC_RETAIN_UNIT = { + STAT_AVERAGE_LINEAR, + STAT_AVERAGE_STEP, + STAT_AVERAGE_TIMELESS, + STAT_CHANGE, + STAT_DISTANCE_95P, + STAT_DISTANCE_99P, + STAT_DISTANCE_ABSOLUTE, + STAT_MEAN, + STAT_MEDIAN, + STAT_NOISINESS, + STAT_STANDARD_DEVIATION, + STAT_TOTAL, + STAT_VALUE_MAX, + STAT_VALUE_MIN, +} + +# Statistics which produce percentage ratio from binary_sensor source entity +STAT_BINARY_PERCENTAGE = { + STAT_AVERAGE_STEP, + STAT_AVERAGE_TIMELESS, + STAT_MEAN, +} CONF_STATE_CHARACTERISTIC = "state_characteristic" CONF_SAMPLES_MAX_BUFFER_SIZE = "sampling_size" @@ -236,7 +262,7 @@ class StatisticsSensor(SensorEntity): samples_max_age: timedelta | None, precision: int, quantile_intervals: int, - quantile_method: str, + quantile_method: Literal["exclusive", "inclusive"], ) -> None: """Initialize the Statistics sensor.""" self._attr_icon: str = ICON @@ -252,7 +278,7 @@ class StatisticsSensor(SensorEntity): self._samples_max_age: timedelta | None = samples_max_age self._precision: int = precision self._quantile_intervals: int = quantile_intervals - self._quantile_method: str = quantile_method + self._quantile_method: Literal["exclusive", "inclusive"] = quantile_method self._value: StateType | datetime = None self._unit_of_measurement: str | None = None self._available: bool = False @@ -336,30 +362,11 @@ class StatisticsSensor(SensorEntity): def _derive_unit_of_measurement(self, new_state: State) -> str | None: base_unit: str | None = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) unit: str | None - if self.is_binary and self._state_characteristic in ( - STAT_AVERAGE_STEP, - STAT_AVERAGE_TIMELESS, - STAT_MEAN, - ): + if self.is_binary and self._state_characteristic in STAT_BINARY_PERCENTAGE: unit = "%" elif not base_unit: unit = None - elif self._state_characteristic in ( - STAT_AVERAGE_LINEAR, - STAT_AVERAGE_STEP, - STAT_AVERAGE_TIMELESS, - STAT_CHANGE, - STAT_DISTANCE_95P, - STAT_DISTANCE_99P, - STAT_DISTANCE_ABSOLUTE, - STAT_MEAN, - STAT_MEDIAN, - STAT_NOISINESS, - STAT_STANDARD_DEVIATION, - STAT_TOTAL, - STAT_VALUE_MAX, - STAT_VALUE_MIN, - ): + elif self._state_characteristic in STAT_NUMERIC_RETAIN_UNIT: unit = base_unit elif self._state_characteristic in STATS_NOT_A_NUMBER: unit = None @@ -374,8 +381,11 @@ class StatisticsSensor(SensorEntity): return unit @property - def device_class(self) -> Literal[SensorDeviceClass.TIMESTAMP] | None: + def device_class(self) -> SensorDeviceClass | None: """Return the class of this device.""" + if self._state_characteristic in STAT_NUMERIC_RETAIN_UNIT: + _state = self.hass.states.get(self._source_entity_id) + return None if _state is None else _state.attributes.get(ATTR_DEVICE_CLASS) if self._state_characteristic in STATS_DATETIME: return SensorDeviceClass.TIMESTAMP return None @@ -485,16 +495,14 @@ class StatisticsSensor(SensorEntity): else: start_date = datetime.fromtimestamp(0, tz=dt_util.UTC) _LOGGER.debug("%s: retrieving all records", self.entity_id) - entity_states = history.state_changes_during_period( + return history.state_changes_during_period( self.hass, start_date, entity_id=lower_entity_id, descending=True, limit=self._samples_max_buffer_size, include_start_time_state=False, - ) - # Need to cast since minimal responses is not passed in - return cast(list[State], entity_states.get(lower_entity_id, [])) + ).get(lower_entity_id, []) async def _initialize_from_database(self) -> None: """Initialize the list of states from the database. diff --git a/homeassistant/components/steam_online/__init__.py b/homeassistant/components/steam_online/__init__.py index 99f384322df..eae81dd8435 100644 --- a/homeassistant/components/steam_online/__init__.py +++ b/homeassistant/components/steam_online/__init__.py @@ -1 +1,48 @@ -"""The steam_online component.""" +"""The Steam integration.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DEFAULT_NAME, DOMAIN +from .coordinator import SteamDataUpdateCoordinator + +PLATFORMS = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Steam from a config entry.""" + coordinator = SteamDataUpdateCoordinator(hass) + await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + + +class SteamEntity(CoordinatorEntity[SteamDataUpdateCoordinator]): + """Representation of a Steam entity.""" + + _attr_attribution = "Data provided by Steam" + + def __init__(self, coordinator: SteamDataUpdateCoordinator) -> None: + """Initialize a Steam entity.""" + super().__init__(coordinator) + self._attr_device_info = DeviceInfo( + configuration_url="https://store.steampowered.com", + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, + manufacturer=DEFAULT_NAME, + name=DEFAULT_NAME, + ) diff --git a/homeassistant/components/steam_online/config_flow.py b/homeassistant/components/steam_online/config_flow.py new file mode 100644 index 00000000000..246d54c0bff --- /dev/null +++ b/homeassistant/components/steam_online/config_flow.py @@ -0,0 +1,194 @@ +"""Config flow for Steam integration.""" +from __future__ import annotations + +from typing import Any + +import steam +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, Platform +from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import config_validation as cv, entity_registry as er +from homeassistant.helpers.typing import ConfigType + +from .const import ( + CONF_ACCOUNT, + CONF_ACCOUNTS, + DEFAULT_NAME, + DOMAIN, + LOGGER, + PLACEHOLDERS, +) + + +def validate_input(user_input: dict[str, str | int]) -> list[dict[str, str | int]]: + """Handle common flow input validation.""" + steam.api.key.set(user_input[CONF_API_KEY]) + interface = steam.api.interface("ISteamUser") + names = interface.GetPlayerSummaries(steamids=user_input[CONF_ACCOUNT]) + return names["response"]["players"]["player"] + + +class SteamFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Steam.""" + + def __init__(self) -> None: + """Initialize the flow.""" + self.entry: config_entries.ConfigEntry | None = None + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Get the options flow for this handler.""" + return SteamOptionsFlowHandler(config_entry) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initiated by the user.""" + errors = {} + if user_input is None and self.entry: + user_input = {CONF_ACCOUNT: self.entry.data[CONF_ACCOUNT]} + elif user_input is not None: + try: + res = await self.hass.async_add_executor_job(validate_input, user_input) + if res[0] is not None: + name = str(res[0]["personaname"]) + else: + errors["base"] = "invalid_account" + except (steam.api.HTTPError, steam.api.HTTPTimeoutError) as ex: + errors["base"] = "cannot_connect" + if "403" in str(ex): + errors["base"] = "invalid_auth" + except Exception as ex: # pylint:disable=broad-except + LOGGER.exception("Unknown exception: %s", ex) + errors["base"] = "unknown" + if not errors: + entry = await self.async_set_unique_id(user_input[CONF_ACCOUNT]) + if entry and self.source == config_entries.SOURCE_REAUTH: + self.hass.config_entries.async_update_entry(entry, data=user_input) + await self.hass.config_entries.async_reload(entry.entry_id) + return self.async_abort(reason="reauth_successful") + self._abort_if_unique_id_configured() + if self.source == config_entries.SOURCE_IMPORT: + accounts_data = { + CONF_ACCOUNTS: { + acc["steamid"]: acc["personaname"] for acc in res + } + } + user_input.pop(CONF_ACCOUNTS) + else: + accounts_data = {CONF_ACCOUNTS: {user_input[CONF_ACCOUNT]: name}} + return self.async_create_entry( + title=name or DEFAULT_NAME, + data=user_input, + options=accounts_data, + ) + user_input = user_input or {} + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_API_KEY, default=user_input.get(CONF_API_KEY) or "" + ): str, + vol.Required( + CONF_ACCOUNT, default=user_input.get(CONF_ACCOUNT) or "" + ): str, + } + ), + errors=errors, + description_placeholders=PLACEHOLDERS, + ) + + async def async_step_import(self, import_config: ConfigType) -> FlowResult: + """Import a config entry from configuration.yaml.""" + for entry in self._async_current_entries(): + if entry.data[CONF_API_KEY] == import_config[CONF_API_KEY]: + return self.async_abort(reason="already_configured") + LOGGER.warning( + "Steam yaml config is now deprecated and has been imported. " + "Please remove it from your config" + ) + import_config[CONF_ACCOUNT] = import_config[CONF_ACCOUNTS][0] + return await self.async_step_user(import_config) + + async def async_step_reauth(self, user_input: dict[str, str]) -> FlowResult: + """Handle a reauthorization flow request.""" + self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: + """Confirm reauth dialog.""" + if user_input is not None: + return await self.async_step_user() + + self._set_confirm_only() + return self.async_show_form( + step_id="reauth_confirm", description_placeholders=PLACEHOLDERS + ) + + +class SteamOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Steam client options.""" + + def __init__(self, entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self.entry = entry + self.options = dict(entry.options) + + async def async_step_init( + self, user_input: dict[str, dict[str, str]] | None = None + ) -> FlowResult: + """Manage Steam options.""" + if user_input is not None: + await self.hass.config_entries.async_unload(self.entry.entry_id) + for _id in self.options[CONF_ACCOUNTS]: + if _id not in user_input[CONF_ACCOUNTS] and ( + entity_id := er.async_get(self.hass).async_get_entity_id( + Platform.SENSOR, DOMAIN, f"sensor.steam_{_id}" + ) + ): + er.async_get(self.hass).async_remove(entity_id) + channel_data = { + CONF_ACCOUNTS: { + _id: name + for _id, name in self.options[CONF_ACCOUNTS].items() + if _id in user_input[CONF_ACCOUNTS] + } + } + await self.hass.config_entries.async_reload(self.entry.entry_id) + return self.async_create_entry(title="", data=channel_data) + try: + users = { + name["steamid"]: name["personaname"] + for name in await self.hass.async_add_executor_job(self.get_accounts) + } + + except steam.api.HTTPTimeoutError: + users = self.options[CONF_ACCOUNTS] + + options = { + vol.Required( + CONF_ACCOUNTS, + default=set(self.options[CONF_ACCOUNTS]), + ): cv.multi_select(users | self.options[CONF_ACCOUNTS]), + } + self.options[CONF_ACCOUNTS] = users | self.options[CONF_ACCOUNTS] + + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) + + def get_accounts(self) -> list[dict[str, str | int]]: + """Get accounts.""" + interface = steam.api.interface("ISteamUser") + friends = interface.GetFriendList(steamid=self.entry.data[CONF_ACCOUNT]) + _users_str = [user["steamid"] for user in friends["friendslist"]["friends"]] + names = interface.GetPlayerSummaries(steamids=_users_str) + return names["response"]["players"]["player"] diff --git a/homeassistant/components/steam_online/const.py b/homeassistant/components/steam_online/const.py new file mode 100644 index 00000000000..01f4410a7c9 --- /dev/null +++ b/homeassistant/components/steam_online/const.py @@ -0,0 +1,38 @@ +"""Steam constants.""" +import logging +from typing import Final + +CONF_ACCOUNT = "account" +CONF_ACCOUNTS = "accounts" + +DATA_KEY_COORDINATOR = "coordinator" +DEFAULT_NAME = "Steam" +DOMAIN: Final = "steam_online" + +LOGGER = logging.getLogger(__package__) + +PLACEHOLDERS = { + "api_key_url": "https://steamcommunity.com/dev/apikey", + "account_id_url": "https://steamid.io", +} + +STATE_OFFLINE = "offline" +STATE_ONLINE = "online" +STATE_BUSY = "busy" +STATE_AWAY = "away" +STATE_SNOOZE = "snooze" +STATE_LOOKING_TO_TRADE = "looking_to_trade" +STATE_LOOKING_TO_PLAY = "looking_to_play" +STEAM_STATUSES = { + 0: STATE_OFFLINE, + 1: STATE_ONLINE, + 2: STATE_BUSY, + 3: STATE_AWAY, + 4: STATE_SNOOZE, + 5: STATE_LOOKING_TO_TRADE, + 6: STATE_LOOKING_TO_PLAY, +} +STEAM_API_URL = "https://steamcdn-a.akamaihd.net/steam/apps/" +STEAM_HEADER_IMAGE_FILE = "header.jpg" +STEAM_MAIN_IMAGE_FILE = "capsule_616x353.jpg" +STEAM_ICON_URL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/" diff --git a/homeassistant/components/steam_online/coordinator.py b/homeassistant/components/steam_online/coordinator.py new file mode 100644 index 00000000000..78c850b0ac9 --- /dev/null +++ b/homeassistant/components/steam_online/coordinator.py @@ -0,0 +1,70 @@ +"""Data update coordinator for the Steam integration.""" +from __future__ import annotations + +from datetime import timedelta + +import steam +from steam.api import _interface_method as INTMethod + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import CONF_ACCOUNTS, DOMAIN, LOGGER + + +class SteamDataUpdateCoordinator(DataUpdateCoordinator): + """Data update coordinator for the Steam integration.""" + + config_entry: ConfigEntry + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the coordinator.""" + super().__init__( + hass=hass, + logger=LOGGER, + name=DOMAIN, + update_interval=timedelta(seconds=30), + ) + self.game_icons: dict[int, str] = {} + self.player_interface: INTMethod = None + self.user_interface: INTMethod = None + steam.api.key.set(self.config_entry.data[CONF_API_KEY]) + + def _update(self) -> dict[str, dict[str, str | int]]: + """Fetch data from API endpoint.""" + accounts = self.config_entry.options[CONF_ACCOUNTS] + _ids = list(accounts) + if not self.user_interface or not self.player_interface: + self.user_interface = steam.api.interface("ISteamUser") + self.player_interface = steam.api.interface("IPlayerService") + if not self.game_icons: + for _id in _ids: + res = self.player_interface.GetOwnedGames( + steamid=_id, include_appinfo=1 + )["response"] + self.game_icons = self.game_icons | { + game["appid"]: game["img_icon_url"] for game in res.get("games", []) + } + response = self.user_interface.GetPlayerSummaries(steamids=_ids) + players = { + player["steamid"]: player + for player in response["response"]["players"]["player"] + if player["steamid"] in _ids + } + for k in players: + data = self.player_interface.GetSteamLevel(steamid=players[k]["steamid"]) + players[k]["level"] = data["response"]["player_level"] + return players + + async def _async_update_data(self) -> dict[str, dict[str, str | int]]: + """Send request to the executor.""" + try: + return await self.hass.async_add_executor_job(self._update) + + except (steam.api.HTTPError, steam.api.HTTPTimeoutError) as ex: + if "401" in str(ex): + raise ConfigEntryAuthFailed from ex + raise UpdateFailed(ex) from ex diff --git a/homeassistant/components/steam_online/manifest.json b/homeassistant/components/steam_online/manifest.json index 47f645d7148..f8aba1aee07 100644 --- a/homeassistant/components/steam_online/manifest.json +++ b/homeassistant/components/steam_online/manifest.json @@ -1,9 +1,10 @@ { "domain": "steam_online", "name": "Steam", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/steam_online", "requirements": ["steamodd==4.21"], - "codeowners": [], + "codeowners": ["@tkdrob"], "iot_class": "cloud_polling", "loggers": ["steam"] } diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index c1f25f4aeff..be175b41b66 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -1,43 +1,37 @@ """Sensor for Steam account status.""" from __future__ import annotations -from datetime import timedelta -import logging -from time import mktime +from datetime import datetime +from time import localtime, mktime -import steam import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_API_KEY -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import track_time_interval -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.util.dt import utc_from_timestamp -_LOGGER = logging.getLogger(__name__) - -CONF_ACCOUNTS = "accounts" - -ICON = "mdi:steam" - -STATE_OFFLINE = "offline" -STATE_ONLINE = "online" -STATE_BUSY = "busy" -STATE_AWAY = "away" -STATE_SNOOZE = "snooze" -STATE_LOOKING_TO_TRADE = "looking_to_trade" -STATE_LOOKING_TO_PLAY = "looking_to_play" - -STEAM_API_URL = "https://steamcdn-a.akamaihd.net/steam/apps/" -STEAM_HEADER_IMAGE_FILE = "header.jpg" -STEAM_MAIN_IMAGE_FILE = "capsule_616x353.jpg" -STEAM_ICON_URL = ( - "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/%d/%s.jpg" +from . import SteamEntity +from .const import ( + CONF_ACCOUNTS, + DOMAIN, + STEAM_API_URL, + STEAM_HEADER_IMAGE_FILE, + STEAM_ICON_URL, + STEAM_MAIN_IMAGE_FILE, + STEAM_STATUSES, ) +from .coordinator import SteamDataUpdateCoordinator +# Deprecated in Home Assistant 2022.5 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_API_KEY): cv.string, @@ -45,186 +39,85 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -APP_LIST_KEY = "steam_online.app_list" -BASE_INTERVAL = timedelta(minutes=1) +PARALLEL_UPDATES = 1 -def setup_platform( +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up the Twitch sensor from yaml.""" + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) + ) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Steam platform.""" - - steam.api.key.set(config[CONF_API_KEY]) - # Initialize steammods app list before creating sensors - # to benefit from internal caching of the list. - hass.data[APP_LIST_KEY] = steam.apps.app_list() - entities = [SteamSensor(account, steam) for account in config[CONF_ACCOUNTS]] - if not entities: - return - add_entities(entities, True) - - # Only one sensor update once every 60 seconds to avoid - # flooding steam and getting disconnected. - entity_next = 0 - - @callback - def do_update(time): - nonlocal entity_next - entities[entity_next].async_schedule_update_ha_state(True) - entity_next = (entity_next + 1) % len(entities) - - track_time_interval(hass, do_update, BASE_INTERVAL) + async_add_entities( + SteamSensor(hass.data[DOMAIN][entry.entry_id], account) + for account in entry.options[CONF_ACCOUNTS] + ) -class SteamSensor(SensorEntity): +class SteamSensor(SteamEntity, SensorEntity): """A class for the Steam account.""" - def __init__(self, account, steamod): + def __init__(self, coordinator: SteamDataUpdateCoordinator, account: str) -> None: """Initialize the sensor.""" - self._steamod = steamod - self._account = account - self._profile = None - self._game = None - self._game_id = None - self._extra_game_info = None - self._state = None - self._name = None - self._avatar = None - self._last_online = None - self._level = None - self._owned_games = None + super().__init__(coordinator) + self.entity_description = SensorEntityDescription( + key=account, + name=f"steam_{account}", + icon="mdi:steam", + ) + self._attr_unique_id = f"sensor.steam_{account}" @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def entity_id(self): - """Return the entity ID.""" - return f"sensor.steam_{self._account}" - - @property - def native_value(self): + def native_value(self) -> StateType: """Return the state of the sensor.""" - return self._state - - @property - def should_poll(self): - """Turn off polling, will do ourselves.""" - return False - - def update(self): - """Update device state.""" - try: - self._profile = self._steamod.user.profile(self._account) - # Only if need be, get the owned games - if not self._owned_games: - self._owned_games = self._steamod.api.interface( - "IPlayerService" - ).GetOwnedGames(steamid=self._account, include_appinfo=1) - - self._game = self._get_current_game() - self._game_id = self._profile.current_game[0] - self._extra_game_info = self._get_game_info() - self._state = { - 1: STATE_ONLINE, - 2: STATE_BUSY, - 3: STATE_AWAY, - 4: STATE_SNOOZE, - 5: STATE_LOOKING_TO_TRADE, - 6: STATE_LOOKING_TO_PLAY, - }.get(self._profile.status, STATE_OFFLINE) - self._name = self._profile.persona - self._avatar = self._profile.avatar_medium - self._last_online = self._get_last_online() - self._level = self._profile.level - except self._steamod.api.HTTPTimeoutError as error: - _LOGGER.warning(error) - self._game = None - self._game_id = None - self._state = None - self._name = None - self._avatar = None - self._last_online = None - self._level = None - - def _get_current_game(self): - """Gather current game name from APP ID.""" - if game_extra_info := self._profile.current_game[2]: - return game_extra_info - - if not (game_id := self._profile.current_game[0]): - return None - - app_list = self.hass.data[APP_LIST_KEY] - try: - _, res = app_list[game_id] - return res - except KeyError: - pass - - # Try reloading the app list, must be a new app - app_list = self._steamod.apps.app_list() - self.hass.data[APP_LIST_KEY] = app_list - try: - _, res = app_list[game_id] - return res - except KeyError: - pass - - _LOGGER.error("Unable to find name of app with ID=%s", game_id) - return repr(game_id) - - def _get_game_info(self): - if (game_id := self._profile.current_game[0]) is not None: - - for game in self._owned_games["response"]["games"]: - if game["appid"] == game_id: - return game - - return None - - def _get_last_online(self): - """Convert last_online from the steam module into timestamp UTC.""" - last_online = utc_from_timestamp(mktime(self._profile.last_online)) - - if last_online: - return last_online - + if self.entity_description.key in self.coordinator.data: + player = self.coordinator.data[self.entity_description.key] + return STEAM_STATUSES[player["personastate"]] return None @property - def extra_state_attributes(self): - """Return the state attributes.""" - attr = {} - if self._game is not None: - attr["game"] = self._game - if self._game_id is not None: - attr["game_id"] = self._game_id - game_url = f"{STEAM_API_URL}{self._game_id}/" - attr["game_image_header"] = f"{game_url}{STEAM_HEADER_IMAGE_FILE}" - attr["game_image_main"] = f"{game_url}{STEAM_MAIN_IMAGE_FILE}" - if self._extra_game_info is not None and self._game_id is not None: - attr["game_icon"] = STEAM_ICON_URL % ( - self._game_id, - self._extra_game_info["img_icon_url"], - ) - if self._last_online is not None: - attr["last_online"] = self._last_online - if self._level is not None: - attr["level"] = self._level - return attr + def extra_state_attributes(self) -> dict[str, str | datetime]: + """Return the state attributes of the sensor.""" + if self.entity_description.key not in self.coordinator.data: + return {} + player = self.coordinator.data[self.entity_description.key] - @property - def entity_picture(self): - """Avatar of the account.""" - return self._avatar + attrs: dict[str, str | datetime] = {} + if game := player.get("gameextrainfo"): + attrs["game"] = game + if game_id := player.get("gameid"): + attrs["game_id"] = game_id + game_url = f"{STEAM_API_URL}{player['gameid']}/" + attrs["game_image_header"] = f"{game_url}{STEAM_HEADER_IMAGE_FILE}" + attrs["game_image_main"] = f"{game_url}{STEAM_MAIN_IMAGE_FILE}" + if info := self._get_game_icon(player): + attrs["game_icon"] = f"{STEAM_ICON_URL}{game_id}/{info}.jpg" + self._attr_name = player["personaname"] + self._attr_entity_picture = player["avatarmedium"] + if last_online := player.get("lastlogoff"): + attrs["last_online"] = utc_from_timestamp(mktime(localtime(last_online))) + if level := self.coordinator.data[self.entity_description.key]["level"]: + attrs["level"] = level + return attrs - @property - def icon(self): - """Return the icon to use in the frontend.""" - return ICON + def _get_game_icon(self, player: dict) -> str | None: + """Get game icon identifier.""" + if player.get("gameid") in self.coordinator.game_icons: + return self.coordinator.game_icons[player["gameid"]] + # Reset game icons to have coordinator get id for new game + self.coordinator.game_icons = {} + return None diff --git a/homeassistant/components/steam_online/strings.json b/homeassistant/components/steam_online/strings.json new file mode 100644 index 00000000000..6d80bb77f1b --- /dev/null +++ b/homeassistant/components/steam_online/strings.json @@ -0,0 +1,36 @@ +{ + "config": { + "step": { + "user": { + "description": "Use {account_id_url} to find your Steam account ID", + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "account": "Steam account ID" + } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The Steam integration needs to be manually re-authenticated\n\nYou can find your key here: {api_key_url}" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "invalid_account": "Invalid account ID", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Names of accounts to be monitored" + } + } + } + } +} diff --git a/homeassistant/components/steam_online/translations/de.json b/homeassistant/components/steam_online/translations/de.json new file mode 100644 index 00000000000..17950e14e24 --- /dev/null +++ b/homeassistant/components/steam_online/translations/de.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_account": "Ung\u00fcltige Konto-ID", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "reauth_confirm": { + "description": "Die Steam-Integration muss manuell erneut authentifiziert werden \n\nDeinen Schl\u00fcssel findest du hier: https://steamcommunity.com/dev/apikey", + "title": "Integration erneut authentifizieren" + }, + "user": { + "data": { + "account": "Steam-Konto-ID", + "api_key": "API-Schl\u00fcssel" + }, + "description": "Verwende https://steamid.io, um deine Steam-Konto-ID zu finden" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Namen der zu \u00fcberwachenden Konten" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/el.json b/homeassistant/components/steam_online/translations/el.json new file mode 100644 index 00000000000..0f598dbc395 --- /dev/null +++ b/homeassistant/components/steam_online/translations/el.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_account": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Steam \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03c5\u03c4\u03b5\u03af \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03bc\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03bf \u03c4\u03c1\u03cc\u03c0\u03bf \n\n \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af \u03c3\u03b1\u03c2 \u03b5\u03b4\u03ce: https://steamcommunity.com/dev/apikey", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03bb\u03b7\u03c0\u03c4\u03b9\u03ba\u03cc\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2" + }, + "user": { + "data": { + "account": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd Steam", + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, + "description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf https://steamid.io \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Steam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "\u039f\u03bd\u03cc\u03bc\u03b1\u03c4\u03b1 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03ce\u03bd \u03c0\u03c1\u03bf\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/en.json b/homeassistant/components/steam_online/translations/en.json new file mode 100644 index 00000000000..990a33dbeff --- /dev/null +++ b/homeassistant/components/steam_online/translations/en.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Service is already configured", + "reauth_successful": "Re-authentication was successful" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_account": "Invalid account ID", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "reauth_confirm": { + "description": "The Steam integration needs to be manually re-authenticated\n\nYou can find your key here: {api_key_url}", + "title": "Reauthenticate Integration" + }, + "user": { + "data": { + "account": "Steam account ID", + "api_key": "API Key" + }, + "description": "Use {account_id_url} to find your Steam account ID" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Names of accounts to be monitored" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/et.json b/homeassistant/components/steam_online/translations/et.json new file mode 100644 index 00000000000..8d501ccf50d --- /dev/null +++ b/homeassistant/components/steam_online/translations/et.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Teenus on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_account": "Kehtetu konto ID", + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "reauth_confirm": { + "description": "Steami sidumine tuleb uuesti autentida\n\nV\u00f5tme leiad siit: https://steamcommunity.com/dev/apikey", + "title": "Taastuvasta sidumine" + }, + "user": { + "data": { + "account": "Steami konto ID", + "api_key": "API v\u00f5ti" + }, + "description": "Kasuta https://steamid.io, et leida oma Steam'i konto ID" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "J\u00e4lgitavate kontode nimed" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/fr.json b/homeassistant/components/steam_online/translations/fr.json new file mode 100644 index 00000000000..5ee6892eef7 --- /dev/null +++ b/homeassistant/components/steam_online/translations/fr.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_account": "ID de compte non valide", + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "reauth_confirm": { + "description": "L'int\u00e9gration Steam doit \u00eatre r\u00e9-authentifi\u00e9e manuellement\n\nVous pouvez trouver votre cl\u00e9 ici\u00a0: https://steamcommunity.com/dev/apikey", + "title": "R\u00e9-authentifier l'int\u00e9gration" + }, + "user": { + "data": { + "account": "ID de compte Steam", + "api_key": "Cl\u00e9 d'API" + }, + "description": "Utilisez https://steamid.io pour trouver l'ID de votre compte Steam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Noms des comptes \u00e0 surveiller" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/hu.json b/homeassistant/components/steam_online/translations/hu.json new file mode 100644 index 00000000000..9bd0976345e --- /dev/null +++ b/homeassistant/components/steam_online/translations/hu.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_account": "\u00c9rv\u00e9nytelen fi\u00f3kazonos\u00edt\u00f3", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth_confirm": { + "description": "A Steam integr\u00e1ci\u00f3t manu\u00e1lisan \u00fajra kell hiteles\u00edteni .\n\nA kulcs itt tal\u00e1lhat\u00f3: https://steamcommunity.com/dev/apikey", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, + "user": { + "data": { + "account": "Steam fi\u00f3k azonos\u00edt\u00f3ja", + "api_key": "API kulcs" + }, + "description": "A https://steamid.io haszn\u00e1lat\u00e1val kaphat\u00f3 meg a Steam fi\u00f3kazonos\u00edt\u00f3" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "A nyomon k\u00f6vetend\u0151 fi\u00f3kok nevei" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/id.json b/homeassistant/components/steam_online/translations/id.json new file mode 100644 index 00000000000..07d708067fc --- /dev/null +++ b/homeassistant/components/steam_online/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_account": "ID akun tidak valid", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth_confirm": { + "description": "Integrasi Steam perlu diautentikasi ulang secara manual \n\n Anda dapat menemukan kunci Anda di sini: https://steamcommunity.com/dev/apikey", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "account": "ID akun Steam", + "api_key": "Kunci API" + }, + "description": "Gunakan https://steamid.io untuk menemukan ID akun Steam Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Nama akun yang akan dipantau" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/it.json b/homeassistant/components/steam_online/translations/it.json new file mode 100644 index 00000000000..71036870961 --- /dev/null +++ b/homeassistant/components/steam_online/translations/it.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_account": "ID account non valido", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "reauth_confirm": { + "description": "L'integrazione di Steam richiede una nuova autenticazione manuale \n\nPuoi trovare la tua chiave qui: https://steamcommunity.com/dev/apikey", + "title": "Autentica nuovamente l'integrazione" + }, + "user": { + "data": { + "account": "ID dell'account Steam", + "api_key": "Chiave API" + }, + "description": "Usa https://steamid.io per trovare l'ID del tuo account Steam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Nomi degli account da monitorare" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/nl.json b/homeassistant/components/steam_online/translations/nl.json new file mode 100644 index 00000000000..20600463f6a --- /dev/null +++ b/homeassistant/components/steam_online/translations/nl.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Service is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_account": "Ongeldige account ID", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "reauth_confirm": { + "description": "De Steam-integratie moet handmatig opnieuw worden geauthenticeerd.\n\nU vind uw API-sleutel hier: https://steamcommunity.com/dev/apikey", + "title": "Verifieer de integratie opnieuw" + }, + "user": { + "data": { + "account": "Steam Account-ID", + "api_key": "API-sleutel" + }, + "description": "Gebruik https://steamid.io om je Steam Account-ID te vinden." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Namen van de accounts die gemonitord moeten worden" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/no.json b/homeassistant/components/steam_online/translations/no.json new file mode 100644 index 00000000000..7e0122dfe18 --- /dev/null +++ b/homeassistant/components/steam_online/translations/no.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_account": "Ugyldig konto-ID", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "reauth_confirm": { + "description": "Steam-integrasjonen m\u00e5 re-autentiseres manuelt \n\n Du finner n\u00f8kkelen din her: https://steamcommunity.com/dev/apikey", + "title": "Godkjenne integrering p\u00e5 nytt" + }, + "user": { + "data": { + "account": "Steam-konto-ID", + "api_key": "API-n\u00f8kkel" + }, + "description": "Bruk https://steamid.io for \u00e5 finne din Steam-konto-ID" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Navn p\u00e5 kontoer som skal overv\u00e5kes" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/pl.json b/homeassistant/components/steam_online/translations/pl.json new file mode 100644 index 00000000000..09c95bca9c4 --- /dev/null +++ b/homeassistant/components/steam_online/translations/pl.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_account": "Niepoprawny identyfikator konta", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "reauth_confirm": { + "description": "Integracja Steam musi zosta\u0107 ponownie uwierzytelniona r\u0119cznie\n\nSw\u00f3j klucz znajdziesz tutaj: https://steamcommunity.com/dev/apikey", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, + "user": { + "data": { + "account": "Identyfikator konta Steam", + "api_key": "Klucz API" + }, + "description": "U\u017cyj https://steamid.io, aby znale\u017a\u0107 sw\u00f3j identyfikator konta Steam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Nazwy kont, kt\u00f3re maj\u0105 by\u0107 monitorowane" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/pt-BR.json b/homeassistant/components/steam_online/translations/pt-BR.json new file mode 100644 index 00000000000..93ff3e00dc5 --- /dev/null +++ b/homeassistant/components/steam_online/translations/pt-BR.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_account": "ID de conta inv\u00e1lido", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "description": "A integra\u00e7\u00e3o da Steam precisa ser autenticada manualmente\n\nVoc\u00ea pode encontrar sua chave aqui: https://steamcommunity.com/dev/apikey", + "title": "Reautenticar Integra\u00e7\u00e3o" + }, + "user": { + "data": { + "account": "ID da conta Steam", + "api_key": "Chave da API" + }, + "description": "Use https://steamid.io para encontrar o ID da sua conta Steam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "Nomes das contas a serem monitoradas" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/ru.json b/homeassistant/components/steam_online/translations/ru.json new file mode 100644 index 00000000000..990226036d9 --- /dev/null +++ b/homeassistant/components/steam_online/translations/ru.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_account": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "reauth_confirm": { + "description": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0439\u0442\u0438 \u0441\u0432\u043e\u0439 \u043a\u043b\u044e\u0447 \u0437\u0434\u0435\u0441\u044c: https://steamcommunity.com/dev/apikey", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + }, + "user": { + "data": { + "account": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Steam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steam_online/translations/zh-Hant.json b/homeassistant/components/steam_online/translations/zh-Hant.json new file mode 100644 index 00000000000..775c9710592 --- /dev/null +++ b/homeassistant/components/steam_online/translations/zh-Hant.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_account": "\u5e33\u865f ID \u7121\u6548", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "reauth_confirm": { + "description": "Steam \u6574\u5408\u9700\u8981\u624b\u52d5\u91cd\u65b0\u8a8d\u8b49\n\n\u53ef\u4ee5\u65bc\u5f8c\u65b9\u7db2\u5740\u627e\u5230\u91d1\u9470\uff1ahttps://steamcommunity.com/dev/apikey", + "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + }, + "user": { + "data": { + "account": "Steam \u5e33\u865f ID", + "api_key": "API \u91d1\u9470" + }, + "description": "\u4f7f\u7528 https://steamid.io \u4ee5\u78ba\u8a8d\u60a8\u7684 Steam \u5e33\u865f ID" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "accounts": "\u6240\u8981\u76e3\u63a7\u7684\u5e33\u865f\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steamist/translations/hu.json b/homeassistant/components/steamist/translations/hu.json index 50cf36394cc..66448b03ab2 100644 --- a/homeassistant/components/steamist/translations/hu.json +++ b/homeassistant/components/steamist/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", "not_steamist_device": "Nem egy seamist k\u00e9sz\u00fcl\u00e9k" diff --git a/homeassistant/components/stiebel_eltron/climate.py b/homeassistant/components/stiebel_eltron/climate.py index 65df643f434..1aab76813ee 100644 --- a/homeassistant/components/stiebel_eltron/climate.py +++ b/homeassistant/components/stiebel_eltron/climate.py @@ -5,12 +5,9 @@ import logging from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_ECO, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -27,19 +24,18 @@ PRESET_DAY = "day" PRESET_SETBACK = "setback" PRESET_EMERGENCY = "emergency" -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE -SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] +SUPPORT_HVAC = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF] SUPPORT_PRESET = [PRESET_ECO, PRESET_DAY, PRESET_EMERGENCY, PRESET_SETBACK] # Mapping STIEBEL ELTRON states to homeassistant states/preset. STE_TO_HA_HVAC = { - "AUTOMATIC": HVAC_MODE_AUTO, - "MANUAL MODE": HVAC_MODE_HEAT, - "STANDBY": HVAC_MODE_AUTO, - "DAY MODE": HVAC_MODE_AUTO, - "SETBACK MODE": HVAC_MODE_AUTO, - "DHW": HVAC_MODE_OFF, - "EMERGENCY OPERATION": HVAC_MODE_AUTO, + "AUTOMATIC": HVACMode.AUTO, + "MANUAL MODE": HVACMode.HEAT, + "STANDBY": HVACMode.AUTO, + "DAY MODE": HVACMode.AUTO, + "SETBACK MODE": HVACMode.AUTO, + "DHW": HVACMode.OFF, + "EMERGENCY OPERATION": HVACMode.AUTO, } STE_TO_HA_PRESET = { @@ -50,9 +46,9 @@ STE_TO_HA_PRESET = { } HA_TO_STE_HVAC = { - HVAC_MODE_AUTO: "AUTOMATIC", - HVAC_MODE_HEAT: "MANUAL MODE", - HVAC_MODE_OFF: "DHW", + HVACMode.AUTO: "AUTOMATIC", + HVACMode.HEAT: "MANUAL MODE", + HVACMode.OFF: "DHW", } HA_TO_STE_PRESET = {k: i for i, k in STE_TO_HA_PRESET.items()} @@ -74,6 +70,11 @@ def setup_platform( class StiebelEltron(ClimateEntity): """Representation of a STIEBEL ELTRON heat pump.""" + _attr_hvac_modes = SUPPORT_HVAC + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, name, ste_data): """Initialize the unit.""" self._name = name @@ -85,11 +86,6 @@ class StiebelEltron(ClimateEntity): self._force_update = False self._ste_data = ste_data - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - def update(self): """Update unit attributes.""" self._ste_data.update(no_throttle=self._force_update) @@ -115,7 +111,7 @@ class StiebelEltron(ClimateEntity): """Return the name of the climate device.""" return self._name - # Handle SUPPORT_TARGET_TEMPERATURE + # Handle ClimateEntityFeature.TARGET_TEMPERATURE @property def temperature_unit(self): """Return the unit of measurement.""" @@ -152,12 +148,7 @@ class StiebelEltron(ClimateEntity): return float(f"{self._current_humidity:.1f}") @property - def hvac_modes(self): - """List of the operation modes.""" - return SUPPORT_HVAC - - @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode | None: """Return current operation ie. heat, cool, idle.""" return STE_TO_HA_HVAC.get(self._operation) @@ -171,7 +162,7 @@ class StiebelEltron(ClimateEntity): """Return a list of available preset modes.""" return SUPPORT_PRESET - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new operation mode.""" if self.preset_mode: return diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 38194d60b3a..f6e00f7c599 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -2,7 +2,7 @@ "domain": "stream", "name": "Stream", "documentation": "https://www.home-assistant.io/integrations/stream", - "requirements": ["PyTurboJPEG==1.6.6", "ha-av==9.1.1-3"], + "requirements": ["PyTurboJPEG==1.6.6", "av==9.2.0"], "dependencies": ["http"], "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "quality_scale": "internal", diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 64a3208edcb..ae1c64396c8 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -60,6 +60,10 @@ def recorder_save_worker(file_out: str, segments: deque[Segment]) -> None: "r", format=SEGMENT_CONTAINER_FORMAT, ) + # Skip this segment if it doesn't have data + if source.duration is None: + source.close() + continue source_v = source.streams.video[0] source_a = source.streams.audio[0] if len(source.streams.audio) > 0 else None @@ -137,7 +141,7 @@ class RecorderOutput(StreamOutput): thread = threading.Thread( name="recorder_save_worker", target=recorder_save_worker, - args=(self.video_path, self._segments), + args=(self.video_path, self._segments.copy()), ) thread.start() diff --git a/homeassistant/components/subaru/lock.py b/homeassistant/components/subaru/lock.py index fb460c6279a..3c619690e96 100644 --- a/homeassistant/components/subaru/lock.py +++ b/homeassistant/components/subaru/lock.py @@ -4,8 +4,11 @@ import logging import voluptuous as vol from homeassistant.components.lock import LockEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import SERVICE_LOCK, SERVICE_UNLOCK +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN, get_device_info from .const import ( @@ -24,7 +27,11 @@ from .remote_service import async_call_remote_service _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the Subaru locks by config_entry.""" entry = hass.data[DOMAIN][config_entry.entry_id] controller = entry[ENTRY_CONTROLLER] diff --git a/homeassistant/components/subaru/translations/ca.json b/homeassistant/components/subaru/translations/ca.json index 6b83af06bb8..31ea78bcd46 100644 --- a/homeassistant/components/subaru/translations/ca.json +++ b/homeassistant/components/subaru/translations/ca.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "El PIN ha de tenir 4 d\u00edgits", + "bad_validation_code_format": "El codi de validaci\u00f3 ha de tenir 6 d\u00edgits", "cannot_connect": "Ha fallat la connexi\u00f3", "incorrect_pin": "PIN incorrecte", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + "incorrect_validation_code": "Codi de validaci\u00f3 inv\u00e0lid", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "two_factor_request_failed": "La sol\u00b7licitud de codi 2FA ha fallat. Torna-ho a provar" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Introdueix el teu PIN de MySubaru\nNOTA: tots els vehicles associats a un compte han de tenir el mateix PIN", "title": "Configuraci\u00f3 de Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Selecciona un m\u00e8tode de contacte:" + }, + "description": "Autenticaci\u00f3 de dos factors requerida", + "title": "Configuraci\u00f3 de Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "Codi de validaci\u00f3" + }, + "description": "Introdueix el codi de validaci\u00f3 rebut", + "title": "Configuraci\u00f3 de Subaru Starlink" + }, "user": { "data": { "country": "Selecciona un pa\u00eds", diff --git a/homeassistant/components/subaru/translations/de.json b/homeassistant/components/subaru/translations/de.json index 4f10a4266ae..2156df0d1a8 100644 --- a/homeassistant/components/subaru/translations/de.json +++ b/homeassistant/components/subaru/translations/de.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "Die PIN sollte 4-stellig sein", + "bad_validation_code_format": "Der Validierungscode sollte 6-stellig sein", "cannot_connect": "Verbindung fehlgeschlagen", "incorrect_pin": "Falsche PIN", - "invalid_auth": "Ung\u00fcltige Authentifizierung" + "incorrect_validation_code": "Falscher Validierungscode", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "two_factor_request_failed": "Anfrage f\u00fcr 2FA-Code fehlgeschlagen, bitte versuche es erneut" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Bitte gib deinen MySubaru-PIN ein\nHINWEIS: Alle Fahrzeuge im Konto m\u00fcssen dieselbe PIN haben", "title": "Subaru Starlink Konfiguration" }, + "two_factor": { + "data": { + "contact_method": "Bitte w\u00e4hle eine Kontaktmethode:" + }, + "description": "Zwei-Faktor-Authentifizierung erforderlich", + "title": "Subaru Starlink Konfiguration" + }, + "two_factor_validate": { + "data": { + "validation_code": "Validierungscode" + }, + "description": "Bitte gib den erhaltenen Validierungscode ein", + "title": "Subaru Starlink Konfiguration" + }, "user": { "data": { "country": "Land ausw\u00e4hlen", diff --git a/homeassistant/components/subaru/translations/el.json b/homeassistant/components/subaru/translations/el.json index 30ff37ccc1e..0376e95225b 100644 --- a/homeassistant/components/subaru/translations/el.json +++ b/homeassistant/components/subaru/translations/el.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "\u03a4\u03bf PIN \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 4 \u03c8\u03b7\u03c6\u03af\u03b1", + "bad_validation_code_format": "\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 6 \u03c8\u03b7\u03c6\u03af\u03c9\u03bd", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "incorrect_pin": "\u039b\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf PIN", - "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2" + "incorrect_validation_code": "\u0395\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "two_factor_request_failed": "\u03a4\u03bf \u03b1\u03af\u03c4\u03b7\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc 2FA \u03b1\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf PIN \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf MySubaru\n\u03a3\u0397\u039c\u0395\u0399\u03a9\u03a3\u0397: \u038c\u03bb\u03b1 \u03c4\u03b1 \u03bf\u03c7\u03ae\u03bc\u03b1\u03c4\u03b1 \u03c3\u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03ad\u03c7\u03bf\u03c5\u03bd \u03c4\u03bf\u03bd \u03af\u03b4\u03b9\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc PIN", "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03af\u03b1\u03c2:" + }, + "description": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03c0\u03b9\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03bb\u03ae\u03c6\u03b8\u03b7", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" + }, "user": { "data": { "country": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c7\u03ce\u03c1\u03b1\u03c2", diff --git a/homeassistant/components/subaru/translations/en.json b/homeassistant/components/subaru/translations/en.json index 722363d4d74..ca2ad3c2c92 100644 --- a/homeassistant/components/subaru/translations/en.json +++ b/homeassistant/components/subaru/translations/en.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN should be 4 digits", + "bad_validation_code_format": "Validation code should be 6 digits", "cannot_connect": "Failed to connect", "incorrect_pin": "Incorrect PIN", - "invalid_auth": "Invalid authentication" + "incorrect_validation_code": "Incorrect validation code", + "invalid_auth": "Invalid authentication", + "two_factor_request_failed": "Request for 2FA code failed, please try again" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Please enter your MySubaru PIN\nNOTE: All vehicles in account must have the same PIN", "title": "Subaru Starlink Configuration" }, + "two_factor": { + "data": { + "contact_method": "Please select a contact method:" + }, + "description": "Two factor authentication required", + "title": "Subaru Starlink Configuration" + }, + "two_factor_validate": { + "data": { + "validation_code": "Validation code" + }, + "description": "Please enter validation code received", + "title": "Subaru Starlink Configuration" + }, "user": { "data": { "country": "Select country", diff --git a/homeassistant/components/subaru/translations/et.json b/homeassistant/components/subaru/translations/et.json index e7390b3b77f..acfa5055402 100644 --- a/homeassistant/components/subaru/translations/et.json +++ b/homeassistant/components/subaru/translations/et.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN-kood peaks olema 4-kohaline", + "bad_validation_code_format": "Kinnituskood peaks olema 6 kohaline", "cannot_connect": "\u00dchendamine nurjus", "incorrect_pin": "Vale PIN-kood", - "invalid_auth": "Vigane autentimine" + "incorrect_validation_code": "Vale kinnituskood", + "invalid_auth": "Vigane autentimine", + "two_factor_request_failed": "2FA koodi taotlus nurjus, proovi uuesti" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Sisesta oma MySubaru PIN-kood\n M\u00c4RKUS. K\u00f5igil kontol olevatel s\u00f5idukitel peab olema sama PIN-kood", "title": "Subaru Starlinki konfiguratsioon" }, + "two_factor": { + "data": { + "contact_method": "Vali kontaktimeetod" + }, + "description": "N\u00f5utav on kaheastmeline autentimine", + "title": "Subaru Starlink s\u00e4tted" + }, + "two_factor_validate": { + "data": { + "validation_code": "Kinnituskood" + }, + "description": "Sisesta saadud kinnituskood", + "title": "Subaru Starlink s\u00e4tted" + }, "user": { "data": { "country": "Vali riik", diff --git a/homeassistant/components/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json index fafcf81157b..a2b7159a6be 100644 --- a/homeassistant/components/subaru/translations/fr.json +++ b/homeassistant/components/subaru/translations/fr.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "Le code PIN doit \u00eatre compos\u00e9 de 4 chiffres", + "bad_validation_code_format": "Le code de validation doit \u00eatre compos\u00e9 de six chiffres", "cannot_connect": "\u00c9chec de connexion", "incorrect_pin": "PIN incorrect", - "invalid_auth": "Authentification non valide" + "incorrect_validation_code": "Code de validation incorrect", + "invalid_auth": "Authentification non valide", + "two_factor_request_failed": "La demande de code 2FA a \u00e9chou\u00e9, veuillez r\u00e9essayer" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Veuillez entrer votre NIP MySubaru\nREMARQUE : Tous les v\u00e9hicules en compte doivent avoir le m\u00eame NIP", "title": "Configuration de Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Veuillez s\u00e9lectionner une m\u00e9thode de contact\u00a0:" + }, + "description": "Authentification \u00e0 deux facteurs requise", + "title": "Configuration Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "Code de validation" + }, + "description": "Veuillez saisir le code de validation re\u00e7u", + "title": "Configuration Subaru Starlink" + }, "user": { "data": { "country": "Choisissez le pays", diff --git a/homeassistant/components/subaru/translations/hu.json b/homeassistant/components/subaru/translations/hu.json index a54ddd57c39..42f9f6bb2c9 100644 --- a/homeassistant/components/subaru/translations/hu.json +++ b/homeassistant/components/subaru/translations/hu.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "A PIN-nek 4 sz\u00e1mjegy\u0171nek kell lennie", + "bad_validation_code_format": "Az \u00e9rv\u00e9nyes\u00edt\u0151 k\u00f3dnak 6 sz\u00e1mjegyb\u0151l kell \u00e1llnia", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "incorrect_pin": "Helytelen PIN", - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + "incorrect_validation_code": "Helytelen \u00e9rv\u00e9nyes\u00edt\u00e9si k\u00f3d", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "two_factor_request_failed": "A k\u00e9tfaktoros hiteles\u00edt\u00e9si k\u00f3d lek\u00e9r\u00e9se sikertelen, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg \u00fajra" }, "step": { "pin": { @@ -18,9 +21,23 @@ "description": "K\u00e9rj\u00fck, adja meg MySubaru PIN-k\u00f3dj\u00e1t\n MEGJEGYZ\u00c9S: A sz\u00e1ml\u00e1n szerepl\u0151 \u00f6sszes j\u00e1rm\u0171nek azonos PIN-k\u00f3ddal kell rendelkeznie", "title": "Subaru Starlink konfigur\u00e1ci\u00f3" }, + "two_factor": { + "data": { + "contact_method": "V\u00e1lasszon kapcsolatfelv\u00e9teli m\u00f3dot:" + }, + "description": "K\u00e9tfaktoros hiteles\u00edt\u00e9s sz\u00fcks\u00e9ges", + "title": "Subaru Starlink konfigur\u00e1ci\u00f3" + }, + "two_factor_validate": { + "data": { + "validation_code": "\u00c9rv\u00e9nyes\u00edt\u00e9si k\u00f3d" + }, + "description": "K\u00e9rj\u00fck, adja meg az \u00e9rv\u00e9nyes\u00edt\u00e9si k\u00f3dot", + "title": "Subaru Starlink konfigur\u00e1ci\u00f3" + }, "user": { "data": { - "country": "V\u00e1lassz orsz\u00e1got", + "country": "V\u00e1lasszon orsz\u00e1got", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, diff --git a/homeassistant/components/subaru/translations/id.json b/homeassistant/components/subaru/translations/id.json index 2a44ec16b92..cf0d37a0c30 100644 --- a/homeassistant/components/subaru/translations/id.json +++ b/homeassistant/components/subaru/translations/id.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN harus terdiri dari 4 angka", + "bad_validation_code_format": "Kode validasi harus terdiri dari 6 angka", "cannot_connect": "Gagal terhubung", "incorrect_pin": "PIN salah", - "invalid_auth": "Autentikasi tidak valid" + "incorrect_validation_code": "Kode validasi salah", + "invalid_auth": "Autentikasi tidak valid", + "two_factor_request_failed": "Permintaan kode 2FA gagal, silakan coba lagi" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Masukkan PIN MySubaru Anda\nCATATAN: Semua kendaraan dalam akun harus memiliki PIN yang sama", "title": "Konfigurasi Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Pilih metode kontak:" + }, + "description": "Diperlukan autentikasi dua faktor", + "title": "Konfigurasi Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "Kode validasi" + }, + "description": "Masukkan kode validasi yang diterima", + "title": "Konfigurasi Subaru Starlink" + }, "user": { "data": { "country": "Pilih negara", diff --git a/homeassistant/components/subaru/translations/it.json b/homeassistant/components/subaru/translations/it.json index a6752b810eb..ea8399dbdc5 100644 --- a/homeassistant/components/subaru/translations/it.json +++ b/homeassistant/components/subaru/translations/it.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "Il PIN deve essere di 4 cifre", + "bad_validation_code_format": "Il codice di convalida deve essere di 6 cifre", "cannot_connect": "Impossibile connettersi", "incorrect_pin": "PIN errato", - "invalid_auth": "Autenticazione non valida" + "incorrect_validation_code": "Codice di convalida errato", + "invalid_auth": "Autenticazione non valida", + "two_factor_request_failed": "Richiesta di codice 2FA non riuscita, riprova" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Inserisci il tuo PIN MySubaru\nNOTA: tutti i veicoli nell'account devono avere lo stesso PIN", "title": "Configurazione Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Seleziona un metodo di contatto:" + }, + "description": "Autenticazione a due fattori richiesta", + "title": "Configurazione Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "Codice di validazione" + }, + "description": "Inserisci il codice di convalida ricevuto", + "title": "Configurazione Subaru Starlink" + }, "user": { "data": { "country": "Seleziona il paese", diff --git a/homeassistant/components/subaru/translations/ja.json b/homeassistant/components/subaru/translations/ja.json index b8949709464..8dcba4b4b23 100644 --- a/homeassistant/components/subaru/translations/ja.json +++ b/homeassistant/components/subaru/translations/ja.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN\u306f4\u6841\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", + "bad_validation_code_format": "\u691c\u8a3c\u30b3\u30fc\u30c9\u306f6\u6841\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "incorrect_pin": "PIN\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093", - "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" + "incorrect_validation_code": "\u691c\u8a3c\u30b3\u30fc\u30c9\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "two_factor_request_failed": "2FA\u30b3\u30fc\u30c9\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "MySubaru PIN\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\n\u6ce8: \u30a2\u30ab\u30a6\u30f3\u30c8\u5185\u306e\u3059\u3079\u3066\u306e\u8eca\u4e21\u306f\u3001\u540c\u3058PIN\u3092\u6301\u3063\u3066\u3044\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059", "title": "Subaru Starlink\u306e\u8a2d\u5b9a" }, + "two_factor": { + "data": { + "contact_method": "\u9023\u7d61\u65b9\u6cd5\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\uff1a" + }, + "description": "\u4e8c\u8981\u7d20\u8a8d\u8a3c\u304c\u5fc5\u8981", + "title": "Subaru Starlink\u306e\u8a2d\u5b9a" + }, + "two_factor_validate": { + "data": { + "validation_code": "\u691c\u8a3c\u30b3\u30fc\u30c9" + }, + "description": "\u53d7\u3051\u53d6\u3063\u305f\u691c\u8a3c\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044", + "title": "Subaru Starlink\u306e\u8a2d\u5b9a" + }, "user": { "data": { "country": "\u56fd\u3092\u9078\u629e", diff --git a/homeassistant/components/subaru/translations/nl.json b/homeassistant/components/subaru/translations/nl.json index 931ed6f967f..256180d444b 100644 --- a/homeassistant/components/subaru/translations/nl.json +++ b/homeassistant/components/subaru/translations/nl.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "De pincode moet uit 4 cijfers bestaan", + "bad_validation_code_format": "De validatiecode moet uit 6 cijfers bestaan", "cannot_connect": "Kan geen verbinding maken", "incorrect_pin": "Onjuiste PIN", - "invalid_auth": "Ongeldige authenticatie" + "incorrect_validation_code": "Onjuiste validatiecode", + "invalid_auth": "Ongeldige authenticatie", + "two_factor_request_failed": "Verzoek om 2FA code mislukt, probeer opnieuw" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Voer uw MySubaru-pincode in\n OPMERKING: Alle voertuigen in een account moeten dezelfde pincode hebben", "title": "Subaru Starlink Configuratie" }, + "two_factor": { + "data": { + "contact_method": "Selecteer een contactmethode:" + }, + "description": "Tweefactorauthenticatie vereist", + "title": "Subaru Starlink-configuratie" + }, + "two_factor_validate": { + "data": { + "validation_code": "Validatiecode" + }, + "description": "Voer de ontvangen validatiecode in", + "title": "Subaru Starlink-configuratie" + }, "user": { "data": { "country": "Selecteer land", diff --git a/homeassistant/components/subaru/translations/no.json b/homeassistant/components/subaru/translations/no.json index 6ac200ec131..3e3a78949db 100644 --- a/homeassistant/components/subaru/translations/no.json +++ b/homeassistant/components/subaru/translations/no.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN-koden skal best\u00e5 av fire sifre", + "bad_validation_code_format": "Valideringskoden skal v\u00e6re p\u00e5 6 sifre", "cannot_connect": "Tilkobling mislyktes", "incorrect_pin": "Feil PIN", - "invalid_auth": "Ugyldig godkjenning" + "incorrect_validation_code": "Feil valideringskode", + "invalid_auth": "Ugyldig godkjenning", + "two_factor_request_failed": "Foresp\u00f8rsel om 2FA-kode mislyktes, pr\u00f8v igjen" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Vennligst skriv inn MySubaru PIN-koden\n MERKNAD: Alle kj\u00f8ret\u00f8yer som er kontoen m\u00e5 ha samme PIN-kode", "title": "Subaru Starlink-konfigurasjon" }, + "two_factor": { + "data": { + "contact_method": "Velg en kontaktmetode:" + }, + "description": "Tofaktorautentisering kreves", + "title": "Subaru Starlink-konfigurasjon" + }, + "two_factor_validate": { + "data": { + "validation_code": "Valideringskode" + }, + "description": "Vennligst skriv inn valideringskode mottatt", + "title": "Subaru Starlink-konfigurasjon" + }, "user": { "data": { "country": "Velg land", diff --git a/homeassistant/components/subaru/translations/pl.json b/homeassistant/components/subaru/translations/pl.json index b0d491d475e..679e5779507 100644 --- a/homeassistant/components/subaru/translations/pl.json +++ b/homeassistant/components/subaru/translations/pl.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN powinien sk\u0142ada\u0107 si\u0119 z 4 cyfr", + "bad_validation_code_format": "Kod weryfikacyjny powinien mie\u0107 6 cyfr", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "incorrect_pin": "Nieprawid\u0142owy PIN", - "invalid_auth": "Niepoprawne uwierzytelnienie" + "incorrect_validation_code": "Nieprawid\u0142owy kod weryfikacyjny", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "two_factor_request_failed": "\u017b\u0105danie kodu 2FA nie powiod\u0142o si\u0119, spr\u00f3buj ponownie" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Wprowad\u017a sw\u00f3j PIN dla MySubaru\nUWAGA: Wszystkie pojazdy na koncie musz\u0105 mie\u0107 ten sam kod PIN", "title": "Konfiguracja Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Prosz\u0119 wybra\u0107 spos\u00f3b kontaktu:" + }, + "description": "Wymagane uwierzytelnianie dwusk\u0142adnikowe", + "title": "Konfiguracja Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "Kod weryfikacyjny" + }, + "description": "Wprowad\u017a otrzymany kod weryfikacyjny", + "title": "Konfiguracja Subaru Starlink" + }, "user": { "data": { "country": "Wybierz kraj", diff --git a/homeassistant/components/subaru/translations/pt-BR.json b/homeassistant/components/subaru/translations/pt-BR.json index e88a9e883c0..e53a16fa33f 100644 --- a/homeassistant/components/subaru/translations/pt-BR.json +++ b/homeassistant/components/subaru/translations/pt-BR.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "O PIN deve ter 4 d\u00edgitos", + "bad_validation_code_format": "O c\u00f3digo de valida\u00e7\u00e3o deve ter 6 d\u00edgitos", "cannot_connect": "Falha ao conectar", "incorrect_pin": "PIN incorreto", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "incorrect_validation_code": "C\u00f3digo de valida\u00e7\u00e3o incorreto", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "two_factor_request_failed": "Falha na solicita\u00e7\u00e3o do c\u00f3digo 2FA, por favor tente novamente" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "Por favor, digite seu PIN MySubaru\n NOTA: Todos os ve\u00edculos em conta devem ter o mesmo PIN", "title": "Configura\u00e7\u00e3o do Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "Selecione um m\u00e9todo de contato:" + }, + "description": "Autentica\u00e7\u00e3o de dois fatores necess\u00e1ria", + "title": "Configura\u00e7\u00e3o do Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "C\u00f3digo de valida\u00e7\u00e3o" + }, + "description": "Insira o c\u00f3digo de valida\u00e7\u00e3o recebido", + "title": "Configura\u00e7\u00e3o do Subaru Starlink" + }, "user": { "data": { "country": "Selecione o pa\u00eds", diff --git a/homeassistant/components/subaru/translations/ru.json b/homeassistant/components/subaru/translations/ru.json index 87f56f8ac8a..45eddd02bc1 100644 --- a/homeassistant/components/subaru/translations/ru.json +++ b/homeassistant/components/subaru/translations/ru.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN-\u043a\u043e\u0434 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 4 \u0446\u0438\u0444\u0440.", + "bad_validation_code_format": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 6 \u0446\u0438\u0444\u0440.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "incorrect_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434.", - "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + "incorrect_validation_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "two_factor_request_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043a\u043e\u0434 2FA. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443." }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434 MySubaru.\n\u0412\u0441\u0435 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0438 \u0432 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 PIN-\u043a\u043e\u0434.", "title": "Subaru Starlink" }, + "two_factor": { + "data": { + "contact_method": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u0432\u044f\u0437\u0438:" + }, + "description": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f", + "title": "Subaru Starlink" + }, + "two_factor_validate": { + "data": { + "validation_code": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f" + }, + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.", + "title": "Subaru Starlink" + }, "user": { "data": { "country": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0443", diff --git a/homeassistant/components/subaru/translations/tr.json b/homeassistant/components/subaru/translations/tr.json index 5724bd4f2c3..3b9f1acc60e 100644 --- a/homeassistant/components/subaru/translations/tr.json +++ b/homeassistant/components/subaru/translations/tr.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN 4 haneli olmal\u0131d\u0131r", + "bad_validation_code_format": "Do\u011frulama kodu 6 basamakl\u0131 olmal\u0131d\u0131r", "cannot_connect": "Ba\u011flanma hatas\u0131", "incorrect_pin": "Yanl\u0131\u015f PIN", - "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + "incorrect_validation_code": "Yanl\u0131\u015f do\u011frulama kodu", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "two_factor_request_failed": "2FA kodu iste\u011fi ba\u015far\u0131s\u0131z oldu, l\u00fctfen tekrar deneyin" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "L\u00fctfen MySubaru PIN'inizi girin\n NOT: Hesaptaki t\u00fcm ara\u00e7lar ayn\u0131 PIN'e sahip olmal\u0131d\u0131r", "title": "Subaru Starlink Yap\u0131land\u0131rmas\u0131" }, + "two_factor": { + "data": { + "contact_method": "L\u00fctfen bir ileti\u015fim y\u00f6ntemi se\u00e7in:" + }, + "description": "\u0130ki fakt\u00f6rl\u00fc kimlik do\u011frulama gerekli", + "title": "Subaru Starlink Yap\u0131land\u0131rmas\u0131" + }, + "two_factor_validate": { + "data": { + "validation_code": "Do\u011frulama kodu" + }, + "description": "L\u00fctfen al\u0131nan do\u011frulama kodunu girin", + "title": "Subaru Starlink Yap\u0131land\u0131rmas\u0131" + }, "user": { "data": { "country": "\u00dclkeyi se\u00e7", diff --git a/homeassistant/components/subaru/translations/zh-Hant.json b/homeassistant/components/subaru/translations/zh-Hant.json index 6264f01791e..2e4a22423e0 100644 --- a/homeassistant/components/subaru/translations/zh-Hant.json +++ b/homeassistant/components/subaru/translations/zh-Hant.json @@ -6,9 +6,12 @@ }, "error": { "bad_pin_format": "PIN \u78bc\u61c9\u8a72\u70ba 4 \u4f4d\u6578\u5b57", + "bad_validation_code_format": "\u9a57\u8b49\u78bc\u5fc5\u9808\u5305\u542b 6 \u4f4d\u6578\u5b57", "cannot_connect": "\u9023\u7dda\u5931\u6557", "incorrect_pin": "PIN \u78bc\u932f\u8aa4", - "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + "incorrect_validation_code": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "two_factor_request_failed": "\u6240\u9700\u96d9\u91cd\u8a8d\u8b49\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21" }, "step": { "pin": { @@ -18,6 +21,20 @@ "description": "\u8acb\u8f38\u5165 MySubaru PIN \u78bc\n\u6ce8\u610f\uff1a\u6240\u4ee5\u5e33\u865f\u5167\u8eca\u8f1b\u90fd\u5fc5\u9808\u4f7f\u7528\u76f8\u540c PIN \u78bc", "title": "Subaru Starlink \u8a2d\u5b9a" }, + "two_factor": { + "data": { + "contact_method": "\u8acb\u9078\u64c7\u806f\u7d61\u65b9\u5f0f\uff1a" + }, + "description": "\u9700\u8981\u96d9\u91cd\u8a8d\u8b49\u78bc", + "title": "Subaru Starlink \u8a2d\u5b9a" + }, + "two_factor_validate": { + "data": { + "validation_code": "\u9a57\u8b49\u78bc" + }, + "description": "\u8acb\u8f38\u5165\u6240\u6536\u5230\u7684\u9a57\u8b49\u78bc", + "title": "Subaru Starlink \u8a2d\u5b9a" + }, "user": { "data": { "country": "\u9078\u64c7\u570b\u5bb6", diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index 94758d4e99f..a30b18befd5 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -5,20 +5,21 @@ import logging from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_ELEVATION, - EVENT_COMPONENT_LOADED, EVENT_CORE_CONFIG_UPDATE, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, ) -from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import event from homeassistant.helpers.entity import Entity +from homeassistant.helpers.integration_platform import ( + async_process_integration_platform_for_component, +) from homeassistant.helpers.sun import ( get_astral_location, get_location_astral_event_next, ) from homeassistant.helpers.typing import ConfigType -from homeassistant.setup import ATTR_COMPONENT from homeassistant.util import dt as dt_util from .const import DOMAIN @@ -95,6 +96,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up from a config entry.""" + # Process integration platforms right away since + # we will create entities before firing EVENT_COMPONENT_LOADED + await async_process_integration_platform_for_component(hass, DOMAIN) hass.data[DOMAIN] = Sun(hass) return True @@ -126,23 +130,10 @@ class Sun(Entity): self._config_listener = None self._update_events_listener = None self._update_sun_position_listener = None - self._loaded_listener = None self._config_listener = self.hass.bus.async_listen( EVENT_CORE_CONFIG_UPDATE, self.update_location ) - if DOMAIN in hass.config.components: - self.update_location() - else: - self._loaded_listener = self.hass.bus.async_listen( - EVENT_COMPONENT_LOADED, self.loading_complete - ) - - @callback - def loading_complete(self, event_: Event) -> None: - """Update location when loading is complete.""" - if event_.data[ATTR_COMPONENT] == DOMAIN: - self.update_location() - self._remove_loaded_listener() + self.update_location() @callback def update_location(self, *_): @@ -156,17 +147,9 @@ class Sun(Entity): self._update_events_listener() self.update_events() - @callback - def _remove_loaded_listener(self): - """Remove the loaded listener.""" - if self._loaded_listener: - self._loaded_listener() - self._loaded_listener = None - @callback def remove_listeners(self): """Remove listeners.""" - self._remove_loaded_listener() if self._config_listener: self._config_listener() if self._update_events_listener: diff --git a/homeassistant/components/sun/translations/bg.json b/homeassistant/components/sun/translations/bg.json index 7b6c5241cd2..81ead95c95f 100644 --- a/homeassistant/components/sun/translations/bg.json +++ b/homeassistant/components/sun/translations/bg.json @@ -1,4 +1,9 @@ { + "config": { + "abort": { + "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + } + }, "state": { "_": { "above_horizon": "\u041d\u0430\u0434 \u0445\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430", diff --git a/homeassistant/components/sun/translations/ca.json b/homeassistant/components/sun/translations/ca.json index 5a49afefc5d..00a37d8b2b3 100644 --- a/homeassistant/components/sun/translations/ca.json +++ b/homeassistant/components/sun/translations/ca.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "step": { + "user": { + "description": "Vols comen\u00e7ar la configuraci\u00f3?" + } + } + }, "state": { "_": { "above_horizon": "Sobre l'horitz\u00f3", diff --git a/homeassistant/components/sun/translations/cs.json b/homeassistant/components/sun/translations/cs.json index fe13d3e4fb0..b4a01f4473e 100644 --- a/homeassistant/components/sun/translations/cs.json +++ b/homeassistant/components/sun/translations/cs.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + }, + "step": { + "user": { + "description": "Chcete za\u010d\u00edt nastavovat?" + } + } + }, "state": { "_": { "above_horizon": "Nad obzorem", diff --git a/homeassistant/components/sun/translations/de.json b/homeassistant/components/sun/translations/de.json index 6b81cf14f76..405c9dac6d4 100644 --- a/homeassistant/components/sun/translations/de.json +++ b/homeassistant/components/sun/translations/de.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "description": "M\u00f6chtest Du mit der Einrichtung beginnen?" + } + } + }, "state": { "_": { "above_horizon": "\u00dcber dem Horizont", diff --git a/homeassistant/components/sun/translations/el.json b/homeassistant/components/sun/translations/el.json index 5079c2476aa..fa3d87d574b 100644 --- a/homeassistant/components/sun/translations/el.json +++ b/homeassistant/components/sun/translations/el.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "step": { + "user": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7;" + } + } + }, "state": { "_": { "above_horizon": "\u03a0\u03ac\u03bd\u03c9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03bf\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1", diff --git a/homeassistant/components/sun/translations/en.json b/homeassistant/components/sun/translations/en.json index 2278e262bb8..367fdab917c 100644 --- a/homeassistant/components/sun/translations/en.json +++ b/homeassistant/components/sun/translations/en.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "user": { + "description": "Do you want to start set up?" + } + } + }, "state": { "_": { "above_horizon": "Above horizon", diff --git a/homeassistant/components/sun/translations/et.json b/homeassistant/components/sun/translations/et.json index 1a4020215f0..d2fe374ddfc 100644 --- a/homeassistant/components/sun/translations/et.json +++ b/homeassistant/components/sun/translations/et.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "step": { + "user": { + "description": "Kas soovid alustada seadistamist?" + } + } + }, "state": { "_": { "above_horizon": "T\u00f5usnud", diff --git a/homeassistant/components/sun/translations/fr.json b/homeassistant/components/sun/translations/fr.json index a878c8022b1..b743a33499d 100644 --- a/homeassistant/components/sun/translations/fr.json +++ b/homeassistant/components/sun/translations/fr.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "user": { + "description": "Voulez-vous commencer la configuration\u00a0?" + } + } + }, "state": { "_": { "above_horizon": "Au-dessus de l'horizon", diff --git a/homeassistant/components/sun/translations/he.json b/homeassistant/components/sun/translations/he.json index 26a2def7d00..2c851de857e 100644 --- a/homeassistant/components/sun/translations/he.json +++ b/homeassistant/components/sun/translations/he.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea." + }, + "step": { + "user": { + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?" + } + } + }, "state": { "_": { "above_horizon": "\u05de\u05e2\u05dc \u05d4\u05d0\u05d5\u05e4\u05e7", diff --git a/homeassistant/components/sun/translations/hu.json b/homeassistant/components/sun/translations/hu.json index 2275d61b35a..402320f02f9 100644 --- a/homeassistant/components/sun/translations/hu.json +++ b/homeassistant/components/sun/translations/hu.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "user": { + "description": "El szeretn\u00e9 kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, "state": { "_": { "above_horizon": "L\u00e1t\u00f3hat\u00e1r felett", diff --git a/homeassistant/components/sun/translations/id.json b/homeassistant/components/sun/translations/id.json index df6c960e67d..789a42556e9 100644 --- a/homeassistant/components/sun/translations/id.json +++ b/homeassistant/components/sun/translations/id.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?" + } + } + }, "state": { "_": { "above_horizon": "Terbit", diff --git a/homeassistant/components/sun/translations/it.json b/homeassistant/components/sun/translations/it.json index fe2c65461cd..48f0e4a8d90 100644 --- a/homeassistant/components/sun/translations/it.json +++ b/homeassistant/components/sun/translations/it.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "step": { + "user": { + "description": "Vuoi iniziare la configurazione?" + } + } + }, "state": { "_": { "above_horizon": "Sopra l'orizzonte", diff --git a/homeassistant/components/sun/translations/ja.json b/homeassistant/components/sun/translations/ja.json index 758beba9dbf..8188e950389 100644 --- a/homeassistant/components/sun/translations/ja.json +++ b/homeassistant/components/sun/translations/ja.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002" + }, + "step": { + "user": { + "description": "\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f" + } + } + }, "state": { "_": { "above_horizon": "\u5730\u5e73\u7dda\u3088\u308a\u4e0a", diff --git a/homeassistant/components/sun/translations/nl.json b/homeassistant/components/sun/translations/nl.json index 6abe34481fa..8c284e4e43d 100644 --- a/homeassistant/components/sun/translations/nl.json +++ b/homeassistant/components/sun/translations/nl.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "step": { + "user": { + "description": "Wilt u beginnen met instellen?" + } + } + }, "state": { "_": { "above_horizon": "Boven de horizon", diff --git a/homeassistant/components/sun/translations/no.json b/homeassistant/components/sun/translations/no.json index 8597fea2ce5..18bb6e25545 100644 --- a/homeassistant/components/sun/translations/no.json +++ b/homeassistant/components/sun/translations/no.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "step": { + "user": { + "description": "Vil du starte oppsettet?" + } + } + }, "state": { "_": { "above_horizon": "Over horisonten", diff --git a/homeassistant/components/sun/translations/pl.json b/homeassistant/components/sun/translations/pl.json index 1f00babd1fd..7e95dd568c6 100644 --- a/homeassistant/components/sun/translations/pl.json +++ b/homeassistant/components/sun/translations/pl.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "step": { + "user": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + } + } + }, "state": { "_": { "above_horizon": "nad horyzontem", diff --git a/homeassistant/components/sun/translations/pt-BR.json b/homeassistant/components/sun/translations/pt-BR.json index 2f060112a0c..fe965b08372 100644 --- a/homeassistant/components/sun/translations/pt-BR.json +++ b/homeassistant/components/sun/translations/pt-BR.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + }, "state": { "_": { "above_horizon": "Acima do horizonte", diff --git a/homeassistant/components/sun/translations/ru.json b/homeassistant/components/sun/translations/ru.json index 7ddf3165aa9..2eca13058de 100644 --- a/homeassistant/components/sun/translations/ru.json +++ b/homeassistant/components/sun/translations/ru.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" + } + } + }, "state": { "_": { "above_horizon": "\u041d\u0430\u0434 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u043e\u043c", diff --git a/homeassistant/components/sun/translations/tr.json b/homeassistant/components/sun/translations/tr.json index 50634454b98..cff510523fa 100644 --- a/homeassistant/components/sun/translations/tr.json +++ b/homeassistant/components/sun/translations/tr.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + }, "state": { "_": { "above_horizon": "G\u00fcnd\u00fcz", diff --git a/homeassistant/components/sun/translations/zh-Hant.json b/homeassistant/components/sun/translations/zh-Hant.json index 4b8da898c70..e3b7cfb9c54 100644 --- a/homeassistant/components/sun/translations/zh-Hant.json +++ b/homeassistant/components/sun/translations/zh-Hant.json @@ -1,4 +1,14 @@ { + "config": { + "abort": { + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" + } + } + }, "state": { "_": { "above_horizon": "\u65e5\u51fa\u6771\u6d77", diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index ac548652953..fd2a5afff1c 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -5,11 +5,7 @@ from typing import Any import voluptuous as vol -from homeassistant.components.light import ( - COLOR_MODE_ONOFF, - PLATFORM_SCHEMA, - LightEntity, -) +from homeassistant.components.light import PLATFORM_SCHEMA, ColorMode, LightEntity from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITY_ID, @@ -63,9 +59,9 @@ async def async_setup_platform( class LightSwitch(LightEntity): """Represents a Switch as a Light.""" - _attr_color_mode = COLOR_MODE_ONOFF + _attr_color_mode = ColorMode.ONOFF _attr_should_poll = False - _attr_supported_color_modes = {COLOR_MODE_ONOFF} + _attr_supported_color_modes = {ColorMode.ONOFF} def __init__(self, name: str, switch_entity_id: str, unique_id: str | None) -> None: """Initialize Light Switch.""" diff --git a/homeassistant/components/switch/translations/cs.json b/homeassistant/components/switch/translations/cs.json index fe398d9b72e..a7b7f35033e 100644 --- a/homeassistant/components/switch/translations/cs.json +++ b/homeassistant/components/switch/translations/cs.json @@ -1,4 +1,14 @@ { + "config": { + "step": { + "init": { + "data": { + "entity_id": "Entita vyp\u00edna\u010de" + }, + "description": "Vyberte vyp\u00edna\u010d pro sv\u011btlo." + } + } + }, "device_automation": { "action_type": { "toggle": "P\u0159epnout {entity_name}", diff --git a/homeassistant/components/switch/translations/el.json b/homeassistant/components/switch/translations/el.json index 2067276af42..986ee7cff92 100644 --- a/homeassistant/components/switch/translations/el.json +++ b/homeassistant/components/switch/translations/el.json @@ -28,8 +28,8 @@ }, "state": { "_": { - "off": "\u039a\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc\u03c2", - "on": "\u0391\u03bd\u03bf\u03b9\u03c7\u03c4\u03cc\u03c2" + "off": "\u039a\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", + "on": "\u0391\u03bd\u03bf\u03b9\u03c7\u03c4\u03cc" } }, "title": "\u0394\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2" diff --git a/homeassistant/components/switch/translations/hu.json b/homeassistant/components/switch/translations/hu.json index 423a48f345d..2dff00040ca 100644 --- a/homeassistant/components/switch/translations/hu.json +++ b/homeassistant/components/switch/translations/hu.json @@ -20,6 +20,7 @@ "is_on": "{entity_name} be van kapcsolva" }, "trigger_type": { + "changed_states": "{entity_name} be- vagy kikapcsolt", "toggled": "{entity_name} \u00e1tkapcsolt", "turned_off": "{entity_name} ki lett kapcsolva", "turned_on": "{entity_name} be lett kapcsolva" diff --git a/homeassistant/components/switch/translations/pt-BR.json b/homeassistant/components/switch/translations/pt-BR.json index 6f7f076332a..0d24cd74bfe 100644 --- a/homeassistant/components/switch/translations/pt-BR.json +++ b/homeassistant/components/switch/translations/pt-BR.json @@ -3,9 +3,9 @@ "step": { "init": { "data": { - "entity_id": "Entidade de switch" + "entity_id": "Entidade de interruptor" }, - "description": "Selecione o switch para o interruptor de luz." + "description": "Selecione o interruptor para a l\u00e2mpada." } } }, diff --git a/homeassistant/components/switch_as_x/config_flow.py b/homeassistant/components/switch_as_x/config_flow.py index a70e0a371e8..991f4f33a6b 100644 --- a/homeassistant/components/switch_as_x/config_flow.py +++ b/homeassistant/components/switch_as_x/config_flow.py @@ -17,25 +17,23 @@ from homeassistant.helpers.schema_config_entry_flow import ( from .const import CONF_TARGET_DOMAIN, DOMAIN +TARGET_DOMAIN_OPTIONS = [ + selector.SelectOptionDict(value=Platform.COVER, label="Cover"), + selector.SelectOptionDict(value=Platform.FAN, label="Fan"), + selector.SelectOptionDict(value=Platform.LIGHT, label="Light"), + selector.SelectOptionDict(value=Platform.LOCK, label="Lock"), + selector.SelectOptionDict(value=Platform.SIREN, label="Siren"), +] + CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = { "user": SchemaFlowFormStep( vol.Schema( { - vol.Required(CONF_ENTITY_ID): selector.selector( - {"entity": {"domain": Platform.SWITCH}} + vol.Required(CONF_ENTITY_ID): selector.EntitySelector( + selector.EntitySelectorConfig(domain=Platform.SWITCH), ), - vol.Required(CONF_TARGET_DOMAIN): selector.selector( - { - "select": { - "options": [ - {"value": Platform.COVER, "label": "Cover"}, - {"value": Platform.FAN, "label": "Fan"}, - {"value": Platform.LIGHT, "label": "Light"}, - {"value": Platform.LOCK, "label": "Lock"}, - {"value": Platform.SIREN, "label": "Siren"}, - ] - } - } + vol.Required(CONF_TARGET_DOMAIN): selector.SelectSelector( + selector.SelectSelectorConfig(options=TARGET_DOMAIN_OPTIONS), ), } ) diff --git a/homeassistant/components/switch_as_x/cover.py b/homeassistant/components/switch_as_x/cover.py index 3825953ed63..4aea836517d 100644 --- a/homeassistant/components/switch_as_x/cover.py +++ b/homeassistant/components/switch_as_x/cover.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity +from homeassistant.components.cover import CoverEntity, CoverEntityFeature from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -48,7 +48,7 @@ async def async_setup_entry( class CoverSwitch(BaseEntity, CoverEntity): """Represents a Switch as a Cover.""" - _attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" diff --git a/homeassistant/components/switch_as_x/light.py b/homeassistant/components/switch_as_x/light.py index 93dba7c8551..1071d6b4480 100644 --- a/homeassistant/components/switch_as_x/light.py +++ b/homeassistant/components/switch_as_x/light.py @@ -1,7 +1,7 @@ """Light support for switch entities.""" from __future__ import annotations -from homeassistant.components.light import COLOR_MODE_ONOFF, LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ENTITY_ID from homeassistant.core import HomeAssistant @@ -39,5 +39,5 @@ async def async_setup_entry( class LightSwitch(BaseToggleEntity, LightEntity): """Represents a Switch as a Light.""" - _attr_color_mode = COLOR_MODE_ONOFF - _attr_supported_color_modes = {COLOR_MODE_ONOFF} + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} diff --git a/homeassistant/components/switch_as_x/siren.py b/homeassistant/components/switch_as_x/siren.py index 752f7fd76ad..591546cd20a 100644 --- a/homeassistant/components/switch_as_x/siren.py +++ b/homeassistant/components/switch_as_x/siren.py @@ -1,8 +1,7 @@ """Siren support for switch entities.""" from __future__ import annotations -from homeassistant.components.siren import SirenEntity -from homeassistant.components.siren.const import SUPPORT_TURN_OFF, SUPPORT_TURN_ON +from homeassistant.components.siren import SirenEntity, SirenEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ENTITY_ID from homeassistant.core import HomeAssistant @@ -40,4 +39,4 @@ async def async_setup_entry( class SirenSwitch(BaseToggleEntity, SirenEntity): """Represents a Switch as a Siren.""" - _attr_supported_features = SUPPORT_TURN_ON | SUPPORT_TURN_OFF + _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF diff --git a/homeassistant/components/switch_as_x/translations/bg.json b/homeassistant/components/switch_as_x/translations/bg.json new file mode 100644 index 00000000000..f5c2a6e0433 --- /dev/null +++ b/homeassistant/components/switch_as_x/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "target_domain": "\u0422\u0438\u043f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/ca.json b/homeassistant/components/switch_as_x/translations/ca.json index 07d6288d33e..f48a744066c 100644 --- a/homeassistant/components/switch_as_x/translations/ca.json +++ b/homeassistant/components/switch_as_x/translations/ca.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Entitat d'interruptor" + } + }, + "user": { "data": { - "entity_id": "Entitat d'interruptor", - "target_domain": "Tipus" + "entity_id": "Commutador", + "target_domain": "Nou tipus" }, - "title": "Converteix un interruptor en \u2026" + "description": "Tria un commutador que vulguis que aparegui a Home Assistant com a llum, coberta o qualsevol altra cosa. El commutador original s'amagar\u00e0.", + "title": "Canvia el tipus de dispositiu commutador" } } }, - "title": "Interruptor com a X" + "title": "Canvia el tipus de dispositiu d'un commutador" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/cs.json b/homeassistant/components/switch_as_x/translations/cs.json new file mode 100644 index 00000000000..11f8471a62a --- /dev/null +++ b/homeassistant/components/switch_as_x/translations/cs.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "config": { + "user": { + "entity_id": "Entita vyp\u00edna\u010de" + } + }, + "user": { + "data": { + "target_domain": "Typ" + }, + "title": "Ud\u011blejte vyp\u00edna\u010dem ..." + } + } + }, + "title": "Zm\u011bna typy vyp\u00edna\u010de" +} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/de.json b/homeassistant/components/switch_as_x/translations/de.json index 63a99ad40e2..1125f4f5a8f 100644 --- a/homeassistant/components/switch_as_x/translations/de.json +++ b/homeassistant/components/switch_as_x/translations/de.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Switch-Entit\u00e4t" + } + }, + "user": { "data": { - "entity_id": "Switch-Entit\u00e4t", - "target_domain": "Typ" + "entity_id": "Schalter", + "target_domain": "Neuer Typ" }, - "title": "Mache einen Schalter zu..." + "description": "W\u00e4hle einen Schalter, der im Home Assistant als Licht, Abdeckung oder sonstiges angezeigt werden soll. Der urspr\u00fcngliche Schalter wird ausgeblendet.", + "title": "Switch-Ger\u00e4tetyp \u00e4ndern" } } }, - "title": "Schalter als X" + "title": "Ger\u00e4tetyp eines Schalters \u00e4ndern" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/el.json b/homeassistant/components/switch_as_x/translations/el.json index 1fb546c13b7..ed10f241927 100644 --- a/homeassistant/components/switch_as_x/translations/el.json +++ b/homeassistant/components/switch_as_x/translations/el.json @@ -1,11 +1,17 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7" + } + }, + "user": { "data": { - "entity_id": "\u039f\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7", + "entity_id": "\u0394\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2", "target_domain": "\u03a4\u03cd\u03c0\u03bf\u03c2" }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf Home Assistant \u03c9\u03c2 \u03c6\u03c9\u03c2, \u03ba\u03ac\u03bb\u03c5\u03bc\u03bc\u03b1 \u03ae \u03bf\u03c4\u03b9\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03ac\u03bb\u03bb\u03bf. \u039f \u03b1\u03c1\u03c7\u03b9\u03ba\u03cc\u03c2 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7\u03c2 \u03b8\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03c1\u03c5\u03bc\u03bc\u03ad\u03bd\u03bf\u03c2.", "title": "\u039a\u03ac\u03bd\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7 \u03ad\u03bd\u03b1 ..." } } diff --git a/homeassistant/components/switch_as_x/translations/en.json b/homeassistant/components/switch_as_x/translations/en.json index 7709a27cf35..4253f0506ef 100644 --- a/homeassistant/components/switch_as_x/translations/en.json +++ b/homeassistant/components/switch_as_x/translations/en.json @@ -1,12 +1,18 @@ { "config": { "step": { + "config": { + "user": { + "entity_id": "Switch entity" + } + }, "user": { "data": { "entity_id": "Switch", "target_domain": "New Type" }, - "description": "Pick a switch that you want to show up in Home Assistant as a light, cover or anything else. The original switch will be hidden." + "description": "Pick a switch that you want to show up in Home Assistant as a light, cover or anything else. The original switch will be hidden.", + "title": "Change switch device type" } } }, diff --git a/homeassistant/components/switch_as_x/translations/et.json b/homeassistant/components/switch_as_x/translations/et.json index 9e18ddced09..9d5a5839fbf 100644 --- a/homeassistant/components/switch_as_x/translations/et.json +++ b/homeassistant/components/switch_as_x/translations/et.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "L\u00fcliti olem" + } + }, + "user": { "data": { - "entity_id": "L\u00fcliti olem", - "target_domain": "T\u00fc\u00fcp" + "entity_id": "L\u00fcliti", + "target_domain": "Uus t\u00fc\u00fcp" }, - "title": "Tee l\u00fcliti ..." + "description": "Vali l\u00fcliti mida soovid Home Assistantis valgustina, avakattena v\u00f5i millegi muuna kuvada. Algne l\u00fcliti peidetakse.", + "title": "Muuda l\u00fclitusseadme t\u00fc\u00fcpi" } } }, - "title": "L\u00fclita kui X" + "title": "Muuda l\u00fcliti t\u00fc\u00fcpi" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/fr.json b/homeassistant/components/switch_as_x/translations/fr.json index 4b5bd6beebe..fa14bd80885 100644 --- a/homeassistant/components/switch_as_x/translations/fr.json +++ b/homeassistant/components/switch_as_x/translations/fr.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Entit\u00e9 du commutateur" + } + }, + "user": { "data": { - "entity_id": "Entit\u00e9 du commutateur", - "target_domain": "Type" + "entity_id": "Interrupteur", + "target_domain": "Nouveau type" }, - "title": "Transformer un commutateur en \u2026" + "description": "Choisissez un interrupteur que vous voulez faire appara\u00eetre dans Home Assistant comme une lumi\u00e8re, une fermeture ou autre. L'interrupteur original sera cach\u00e9.", + "title": "Modifier le type d'appareil de l'interrupteur" } } }, - "title": "Commutateur en tant que X" + "title": "Modifier le type d'appareil d'un commutateur" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/he.json b/homeassistant/components/switch_as_x/translations/he.json index 8ca833876fe..39889dab709 100644 --- a/homeassistant/components/switch_as_x/translations/he.json +++ b/homeassistant/components/switch_as_x/translations/he.json @@ -1,8 +1,8 @@ { "config": { "step": { - "init": { - "data": { + "config": { + "user": { "entity_id": "\u05d9\u05e9\u05d5\u05ea \u05de\u05ea\u05d2" } } diff --git a/homeassistant/components/switch_as_x/translations/hu.json b/homeassistant/components/switch_as_x/translations/hu.json index b3ea0af39fd..0d919f5ecd3 100644 --- a/homeassistant/components/switch_as_x/translations/hu.json +++ b/homeassistant/components/switch_as_x/translations/hu.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Kapcsol\u00f3 entit\u00e1s" + } + }, + "user": { "data": { - "entity_id": "Kapcsol\u00f3 entit\u00e1s", - "target_domain": "T\u00edpus" + "entity_id": "Kapcsol\u00f3", + "target_domain": "\u00daj t\u00edpus" }, - "title": "Kapcsol\u00f3 mint..." + "description": "V\u00e1lassza ki azt a kapcsol\u00f3t, amelyet meg szeretne jelen\u00edteni a Home Assistantban l\u00e1mpak\u00e9nt, red\u0151nyk\u00e9nt vagy b\u00e1rmi m\u00e1sk\u00e9nt. Az eredeti kapcsol\u00f3 el lesz rejtve.", + "title": "Kapcsol\u00f3 eszk\u00f6zt\u00edpus m\u00f3dos\u00edt\u00e1sa" } } }, - "title": "Kapcsol\u00f3 mint X" + "title": "Kapcsol\u00f3 mint m\u00e1s eszk\u00f6z" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/id.json b/homeassistant/components/switch_as_x/translations/id.json index 1a5cce08f01..47d31e26c03 100644 --- a/homeassistant/components/switch_as_x/translations/id.json +++ b/homeassistant/components/switch_as_x/translations/id.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Entitas saklar" + } + }, + "user": { "data": { - "entity_id": "Entitas saklar", - "target_domain": "Jenis" + "entity_id": "Sakelar", + "target_domain": "Tipe Baru" }, - "title": "Jadikan saklar sebagai\u2026" + "description": "Pilih sakelar yang ingin Anda tampilkan di Home Assistant sebagai lampu, penutup, atau apa pun. Sakelar asli akan disembunyikan.", + "title": "Ubah jenis perangkat sakelar" } } }, - "title": "Saklar sebagai X" + "title": "Ubah jenis perangkat sakelar" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/it.json b/homeassistant/components/switch_as_x/translations/it.json index 1ef154b0578..4706b4d9ece 100644 --- a/homeassistant/components/switch_as_x/translations/it.json +++ b/homeassistant/components/switch_as_x/translations/it.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Cambia entit\u00e0" + } + }, + "user": { "data": { - "entity_id": "Cambia entit\u00e0", - "target_domain": "Tipo" + "entity_id": "Interruttore", + "target_domain": "Nuovo tipo" }, - "title": "Rendi un interruttore un..." + "description": "Scegli un interruttore che vuoi mostrare in Home Assistant come luce, copertura o qualsiasi altra cosa. L'interruttore originale sar\u00e0 nascosto.", + "title": "Cambia tipo di dispositivo dell'interruttore" } } }, - "title": "Interruttore come X" + "title": "Cambia il tipo di dispositivo di un interruttore" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/ja.json b/homeassistant/components/switch_as_x/translations/ja.json index 44ceaecdd75..434e1b588d5 100644 --- a/homeassistant/components/switch_as_x/translations/ja.json +++ b/homeassistant/components/switch_as_x/translations/ja.json @@ -1,11 +1,17 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "\u30b9\u30a4\u30c3\u30c1\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3" + } + }, + "user": { "data": { - "entity_id": "\u30b9\u30a4\u30c3\u30c1\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3", + "entity_id": "\u30b9\u30a4\u30c3\u30c1", "target_domain": "\u30bf\u30a4\u30d7" }, + "description": "Home Assistant\u306b\u3001\u30e9\u30a4\u30c8\u3084\u30ab\u30d0\u30fc\u306a\u3069\u306e\u8868\u793a\u3055\u305b\u305f\u3044\u30b9\u30a4\u30c3\u30c1\u3092\u9078\u3073\u307e\u3059\u3002\u5143\u306e\u30b9\u30a4\u30c3\u30c1\u306f\u975e\u8868\u793a\u306b\u306a\u308a\u307e\u3059\u3002", "title": "\u30b9\u30a4\u30c3\u30c1\u3092..." } } diff --git a/homeassistant/components/switch_as_x/translations/nl.json b/homeassistant/components/switch_as_x/translations/nl.json index b1712904a76..4fcea818b9a 100644 --- a/homeassistant/components/switch_as_x/translations/nl.json +++ b/homeassistant/components/switch_as_x/translations/nl.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Entiteit wijzigen" + } + }, + "user": { "data": { - "entity_id": "Entiteit wijzigen", - "target_domain": "Type" + "entity_id": "Schakelaar", + "target_domain": "Nieuw type" }, - "title": "Schakel een..." + "description": "Kies een schakelaar die u in Home Assistant wilt laten verschijnen als licht, klep of iets anders. De oorspronkelijke schakelaar wordt verborgen.", + "title": "Type schakelapparaat wijzigen" } } }, - "title": "Schakelen als X" + "title": "Apparaattype van een schakelaar wijzigen" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/no.json b/homeassistant/components/switch_as_x/translations/no.json index 09d8d4e813e..5958c87f201 100644 --- a/homeassistant/components/switch_as_x/translations/no.json +++ b/homeassistant/components/switch_as_x/translations/no.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Bytt enhet" + } + }, + "user": { "data": { - "entity_id": "Bytt enhet", - "target_domain": "Type" + "entity_id": "Bryter", + "target_domain": "Ny type" }, - "title": "Gj\u00f8r en bryter til en ..." + "description": "Velg en bryter du vil vise i Home Assistant som lys, deksel eller noe annet. Den opprinnelige bryteren vil v\u00e6re skjult.", + "title": "Endre bryterenhetstype" } } }, - "title": "Bryter som X" + "title": "Endre enhetstype for en bryter" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/pl.json b/homeassistant/components/switch_as_x/translations/pl.json index c3f9af2601d..7491ef0d7fc 100644 --- a/homeassistant/components/switch_as_x/translations/pl.json +++ b/homeassistant/components/switch_as_x/translations/pl.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Encja prze\u0142\u0105cznika" + } + }, + "user": { "data": { - "entity_id": "Encja prze\u0142\u0105cznika", - "target_domain": "Rodzaj" + "entity_id": "Prze\u0142\u0105cznik", + "target_domain": "Nowy rodzaj" }, - "title": "Zmie\u0144 prze\u0142\u0105cznik na ..." + "description": "Wybierz prze\u0142\u0105cznik, kt\u00f3ry chcesz pokaza\u0107 w Home Assistant jako \u015bwiat\u0142o, rolet\u0119 lub cokolwiek innego. Oryginalny prze\u0142\u0105cznik zostanie ukryty.", + "title": "Zmiana typu urz\u0105dzenia prze\u0142\u0105czaj\u0105cego" } } }, - "title": "Prze\u0142\u0105cznik jako \"X\"" + "title": "Zmiana typu urz\u0105dzenia w prze\u0142\u0105czniku" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/pt-BR.json b/homeassistant/components/switch_as_x/translations/pt-BR.json index e8ccabe7841..bf8bc276781 100644 --- a/homeassistant/components/switch_as_x/translations/pt-BR.json +++ b/homeassistant/components/switch_as_x/translations/pt-BR.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Entidade de interruptor" + } + }, + "user": { "data": { - "entity_id": "Entidade de interruptor", - "target_domain": "Tipo" + "entity_id": "Interruptor", + "target_domain": "Novo tipo" }, - "title": "Fa\u00e7a um interruptor um ..." + "description": "Escolha um interruptor que voc\u00ea deseja que apare\u00e7a no Home Assistant como luz, cortina ou qualquer outra coisa. A op\u00e7\u00e3o original ficar\u00e1 oculta.", + "title": "Alterar o tipo de dispositivo do interruptor" } } }, - "title": "Switch as X" + "title": "Alterar o tipo de dispositivo de um interruptor" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/pt.json b/homeassistant/components/switch_as_x/translations/pt.json index 558030dc42d..70f238e0a08 100644 --- a/homeassistant/components/switch_as_x/translations/pt.json +++ b/homeassistant/components/switch_as_x/translations/pt.json @@ -1,7 +1,7 @@ { "config": { "step": { - "init": { + "user": { "data": { "target_domain": "Tipo" } diff --git a/homeassistant/components/switch_as_x/translations/ru.json b/homeassistant/components/switch_as_x/translations/ru.json index b4136768f03..3074365dd76 100644 --- a/homeassistant/components/switch_as_x/translations/ru.json +++ b/homeassistant/components/switch_as_x/translations/ru.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "\u0412\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c" + } + }, + "user": { "data": { "entity_id": "\u0412\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c", - "target_domain": "\u0422\u0438\u043f" + "target_domain": "\u041d\u043e\u0432\u044b\u0439 \u0442\u0438\u043f" }, - "title": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u043a\u0430\u043a \u2026" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0432 Home Assistant \u043a\u0430\u043a \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u0435, \u0448\u0442\u043e\u0440\u044b \u0438\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0435\u0449\u0451. \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u0441\u043a\u0440\u044b\u0442.", + "title": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0442\u0438\u043f \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044f" } } }, - "title": "\u0412\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044c \u043a\u0430\u043a \u2026" + "title": "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u044f" } \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/sv.json b/homeassistant/components/switch_as_x/translations/sv.json index 835de2f5a7f..96419c39bf1 100644 --- a/homeassistant/components/switch_as_x/translations/sv.json +++ b/homeassistant/components/switch_as_x/translations/sv.json @@ -1,9 +1,13 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "Kontakt-entitet" + } + }, + "user": { "data": { - "entity_id": "Kontakt-entitet", "target_domain": "Typ" }, "title": "G\u00f6r en kontakt till ..." diff --git a/homeassistant/components/switch_as_x/translations/tr.json b/homeassistant/components/switch_as_x/translations/tr.json new file mode 100644 index 00000000000..b793be6baf0 --- /dev/null +++ b/homeassistant/components/switch_as_x/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "config": { + "user": { + "entity_id": "Varl\u0131\u011f\u0131 de\u011fi\u015ftir" + } + }, + "user": { + "data": { + "entity_id": "Anahtar", + "target_domain": "Yeni T\u00fcr" + }, + "description": "Home Assistant'ta \u0131\u015f\u0131k, \u00f6rt\u00fc veya ba\u015fka bir \u015fey olarak g\u00f6r\u00fcnmesini istedi\u011finiz bir anahtar se\u00e7in. Orijinal anahtar gizlenecektir.", + "title": "Anahtar cihaz t\u00fcr\u00fcn\u00fc de\u011fi\u015ftir" + } + } + }, + "title": "Bir anahtar\u0131n cihaz t\u00fcr\u00fcn\u00fc de\u011fi\u015ftirme" +} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/zh-Hans.json b/homeassistant/components/switch_as_x/translations/zh-Hans.json new file mode 100644 index 00000000000..e765a436849 --- /dev/null +++ b/homeassistant/components/switch_as_x/translations/zh-Hans.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "config": { + "user": { + "entity_id": "\u5f00\u5173\u5b9e\u4f53" + } + }, + "user": { + "data": { + "entity_id": "\u5f00\u5173", + "target_domain": "\u65b0\u7c7b\u578b" + }, + "description": "\u9009\u62e9\u4e00\u4e2a\u5f00\u5173\uff0c\u8ba9\u5b83\u5728 Home Assistant \u4e2d\u663e\u793a\u4e3a\u706f\u3001\u5377\u5e18\u7b49\u5404\u79cd\u7c7b\u578b\u3002\u539f\u6765\u7684\u5f00\u5173\u5c06\u88ab\u9690\u85cf\u3002", + "title": "\u66f4\u6539\u5f00\u5173\u7684\u8bbe\u5907\u7c7b\u578b" + } + } + }, + "title": "\u66f4\u6539\u5f00\u5173\u7684\u8bbe\u5907\u7c7b\u578b" +} \ No newline at end of file diff --git a/homeassistant/components/switch_as_x/translations/zh-Hant.json b/homeassistant/components/switch_as_x/translations/zh-Hant.json index 231f5b58eff..bd6a1e15ba0 100644 --- a/homeassistant/components/switch_as_x/translations/zh-Hant.json +++ b/homeassistant/components/switch_as_x/translations/zh-Hant.json @@ -1,14 +1,20 @@ { "config": { "step": { - "init": { + "config": { + "user": { + "entity_id": "\u958b\u95dc\u5be6\u9ad4" + } + }, + "user": { "data": { - "entity_id": "\u958b\u95dc\u5be6\u9ad4", - "target_domain": "\u985e\u5225" + "entity_id": "\u958b\u95dc", + "target_domain": "\u65b0\u589e\u985e\u5225" }, - "title": "\u5c07\u958b\u95dc\u8a2d\u5b9a\u70ba ..." + "description": "\u9078\u64c7\u6240\u8981\u65bc Home Assistant \u4e2d\u986f\u793a\u70ba\u71c8\u5149\u7684\u958b\u95dc\u3001\u7a97\u7c3e\u6216\u5176\u4ed6\u5be6\u9ad4\u3002\u539f\u59cb\u958b\u95dc\u5c07\u6703\u9032\u884c\u96b1\u85cf\u3002", + "title": "\u8b8a\u66f4\u958b\u95dc\u985e\u5225" } } }, - "title": "\u958b\u95dc\u8a2d\u70ba X" + "title": "\u8b8a\u66f4\u958b\u95dc\u985e\u5225" } \ No newline at end of file diff --git a/homeassistant/components/switchbot/cover.py b/homeassistant/components/switchbot/cover.py index 97ca1aa15bb..9f265d696ad 100644 --- a/homeassistant/components/switchbot/cover.py +++ b/homeassistant/components/switchbot/cover.py @@ -9,12 +9,9 @@ from switchbot import SwitchbotCurtain # pylint: disable=import-error from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MAC, CONF_NAME, CONF_PASSWORD @@ -61,7 +58,10 @@ class SwitchBotCurtainEntity(SwitchbotEntity, CoverEntity, RestoreEntity): _attr_device_class = CoverDeviceClass.CURTAIN _attr_supported_features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION ) _attr_assumed_state = True diff --git a/homeassistant/components/switchbot/translations/fr.json b/homeassistant/components/switchbot/translations/fr.json index 41b52f253ac..aca51b4e4c4 100644 --- a/homeassistant/components/switchbot/translations/fr.json +++ b/homeassistant/components/switchbot/translations/fr.json @@ -29,7 +29,7 @@ "retry_count": "Nombre de nouvelles tentatives", "retry_timeout": "D\u00e9lai d'attente entre les tentatives", "scan_timeout": "Dur\u00e9e de la recherche de donn\u00e9es publicitaires", - "update_time": "Dur\u00e9e (en secondes) entre deux mises \u00e0 jour" + "update_time": "Intervalle de temps entre deux mises \u00e0 jour (en secondes)" } } } diff --git a/homeassistant/components/switchbot/translations/hu.json b/homeassistant/components/switchbot/translations/hu.json index 5af1acb5d35..2b80fbedbd8 100644 --- a/homeassistant/components/switchbot/translations/hu.json +++ b/homeassistant/components/switchbot/translations/hu.json @@ -17,7 +17,7 @@ "user": { "data": { "mac": "Eszk\u00f6z MAC-c\u00edme", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3" }, "title": "Switchbot eszk\u00f6z be\u00e1ll\u00edt\u00e1sa" diff --git a/homeassistant/components/switchbot/translations/it.json b/homeassistant/components/switchbot/translations/it.json index 68071ec94e5..f589046d4db 100644 --- a/homeassistant/components/switchbot/translations/it.json +++ b/homeassistant/components/switchbot/translations/it.json @@ -9,8 +9,8 @@ }, "error": { "cannot_connect": "Impossibile connettersi", - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "flow_title": "{name}", "step": { diff --git a/homeassistant/components/syncthing/translations/ca.json b/homeassistant/components/syncthing/translations/ca.json index a10b5d8c134..e6a1625159d 100644 --- a/homeassistant/components/syncthing/translations/ca.json +++ b/homeassistant/components/syncthing/translations/ca.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/de.json b/homeassistant/components/syncthing/translations/de.json index 06db3e89b97..a753ae08cd9 100644 --- a/homeassistant/components/syncthing/translations/de.json +++ b/homeassistant/components/syncthing/translations/de.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/el.json b/homeassistant/components/syncthing/translations/el.json index 7d8c1e635df..d31063f30d3 100644 --- a/homeassistant/components/syncthing/translations/el.json +++ b/homeassistant/components/syncthing/translations/el.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/en.json b/homeassistant/components/syncthing/translations/en.json index 68efde737f2..e25581817a9 100644 --- a/homeassistant/components/syncthing/translations/en.json +++ b/homeassistant/components/syncthing/translations/en.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/es.json b/homeassistant/components/syncthing/translations/es.json index 550c1874010..e39a1436c2a 100644 --- a/homeassistant/components/syncthing/translations/es.json +++ b/homeassistant/components/syncthing/translations/es.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/et.json b/homeassistant/components/syncthing/translations/et.json index 12922ad3f6d..06addc30e2f 100644 --- a/homeassistant/components/syncthing/translations/et.json +++ b/homeassistant/components/syncthing/translations/et.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/fr.json b/homeassistant/components/syncthing/translations/fr.json index f0d93986445..75589f10740 100644 --- a/homeassistant/components/syncthing/translations/fr.json +++ b/homeassistant/components/syncthing/translations/fr.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Synchroniser" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/he.json b/homeassistant/components/syncthing/translations/he.json index 7e87dacd7e5..5df905492f0 100644 --- a/homeassistant/components/syncthing/translations/he.json +++ b/homeassistant/components/syncthing/translations/he.json @@ -16,6 +16,5 @@ } } } - }, - "title": "\u05e1\u05d9\u05e0\u05db\u05e8\u05d5\u05df" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/hu.json b/homeassistant/components/syncthing/translations/hu.json index 59ed4021b3f..90aca8becea 100644 --- a/homeassistant/components/syncthing/translations/hu.json +++ b/homeassistant/components/syncthing/translations/hu.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Szinkroniz\u00e1l\u00e1s" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/id.json b/homeassistant/components/syncthing/translations/id.json index d8f2a76c41d..db83504f4af 100644 --- a/homeassistant/components/syncthing/translations/id.json +++ b/homeassistant/components/syncthing/translations/id.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/it.json b/homeassistant/components/syncthing/translations/it.json index 0973a3ccf92..061be57e295 100644 --- a/homeassistant/components/syncthing/translations/it.json +++ b/homeassistant/components/syncthing/translations/it.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/ja.json b/homeassistant/components/syncthing/translations/ja.json index 482087ed730..2a725cbf3cc 100644 --- a/homeassistant/components/syncthing/translations/ja.json +++ b/homeassistant/components/syncthing/translations/ja.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/nl.json b/homeassistant/components/syncthing/translations/nl.json index 358490c6c83..4f66222ada8 100644 --- a/homeassistant/components/syncthing/translations/nl.json +++ b/homeassistant/components/syncthing/translations/nl.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/no.json b/homeassistant/components/syncthing/translations/no.json index 9fdb9f5f305..00a4de27e7f 100644 --- a/homeassistant/components/syncthing/translations/no.json +++ b/homeassistant/components/syncthing/translations/no.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Synkronisering" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/pl.json b/homeassistant/components/syncthing/translations/pl.json index e4e2619f1eb..367b1522930 100644 --- a/homeassistant/components/syncthing/translations/pl.json +++ b/homeassistant/components/syncthing/translations/pl.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/pt-BR.json b/homeassistant/components/syncthing/translations/pt-BR.json index 08f66569d93..d7f246e1aa6 100644 --- a/homeassistant/components/syncthing/translations/pt-BR.json +++ b/homeassistant/components/syncthing/translations/pt-BR.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Sincroniza\u00e7\u00e3o" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/ru.json b/homeassistant/components/syncthing/translations/ru.json index 337b0214c6b..60897aade50 100644 --- a/homeassistant/components/syncthing/translations/ru.json +++ b/homeassistant/components/syncthing/translations/ru.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/sv.json b/homeassistant/components/syncthing/translations/sv.json index 7862919ad54..9be5ea00256 100644 --- a/homeassistant/components/syncthing/translations/sv.json +++ b/homeassistant/components/syncthing/translations/sv.json @@ -9,6 +9,5 @@ } } } - }, - "title": "Synkronisering" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/tr.json b/homeassistant/components/syncthing/translations/tr.json index eeeb7e02c3c..e490274eb25 100644 --- a/homeassistant/components/syncthing/translations/tr.json +++ b/homeassistant/components/syncthing/translations/tr.json @@ -17,6 +17,5 @@ } } } - }, - "title": "E\u015fitleme" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/zh-Hans.json b/homeassistant/components/syncthing/translations/zh-Hans.json index 87d3db5c83f..96af1e51799 100644 --- a/homeassistant/components/syncthing/translations/zh-Hans.json +++ b/homeassistant/components/syncthing/translations/zh-Hans.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthing/translations/zh-Hant.json b/homeassistant/components/syncthing/translations/zh-Hant.json index f06c16ac157..6b6bc948c5f 100644 --- a/homeassistant/components/syncthing/translations/zh-Hant.json +++ b/homeassistant/components/syncthing/translations/zh-Hant.json @@ -17,6 +17,5 @@ } } } - }, - "title": "Syncthing" + } } \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/hu.json b/homeassistant/components/syncthru/translations/hu.json index a5b645200db..3de6022dcb6 100644 --- a/homeassistant/components/syncthru/translations/hu.json +++ b/homeassistant/components/syncthru/translations/hu.json @@ -12,13 +12,13 @@ "step": { "confirm": { "data": { - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "url": "Webes fel\u00fclet URL-je" } }, "user": { "data": { - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "url": "Webes fel\u00fclet URL-je" } } diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 8eed02118c0..1151bf128cc 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -28,12 +28,7 @@ from homeassistant.helpers.device_registry import ( DeviceEntry, async_get_registry as get_dev_reg, ) -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, - UpdateFailed, -) +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .common import SynoApi from .const import ( @@ -49,16 +44,12 @@ from .const import ( SYNO_API, SYSTEM_LOADED, UNDO_UPDATE_LISTENER, - SynologyDSMEntityDescription, ) from .service import async_setup_services CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) -ATTRIBUTION = "Data provided by Synology" - - _LOGGER = logging.getLogger(__name__) @@ -223,111 +214,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) - - -class SynologyDSMBaseEntity( - CoordinatorEntity[DataUpdateCoordinator[dict[str, dict[str, Any]]]] -): - """Representation of a Synology NAS entry.""" - - entity_description: SynologyDSMEntityDescription - unique_id: str - _attr_attribution = ATTRIBUTION - - def __init__( - self, - api: SynoApi, - coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]], - description: SynologyDSMEntityDescription, - ) -> None: - """Initialize the Synology DSM entity.""" - super().__init__(coordinator) - self.entity_description = description - - self._api = api - self._attr_name = f"{api.network.hostname} {description.name}" - self._attr_unique_id: str = ( - f"{api.information.serial}_{description.api_key}:{description.key}" - ) - - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - return DeviceInfo( - identifiers={(DOMAIN, self._api.information.serial)}, - name="Synology NAS", - manufacturer="Synology", - model=self._api.information.model, - sw_version=self._api.information.version_string, - configuration_url=self._api.config_url, - ) - - async def async_added_to_hass(self) -> None: - """Register entity for updates from API.""" - self.async_on_remove( - self._api.subscribe(self.entity_description.api_key, self.unique_id) - ) - await super().async_added_to_hass() - - -class SynologyDSMDeviceEntity(SynologyDSMBaseEntity): - """Representation of a Synology NAS disk or volume entry.""" - - def __init__( - self, - api: SynoApi, - coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]], - description: SynologyDSMEntityDescription, - device_id: str | None = None, - ) -> None: - """Initialize the Synology DSM disk or volume entity.""" - super().__init__(api, coordinator, description) - self._device_id = device_id - self._device_name: str | None = None - self._device_manufacturer: str | None = None - self._device_model: str | None = None - self._device_firmware: str | None = None - self._device_type = None - - if "volume" in description.key: - volume = self._api.storage.get_volume(self._device_id) - # Volume does not have a name - self._device_name = volume["id"].replace("_", " ").capitalize() - self._device_manufacturer = "Synology" - self._device_model = self._api.information.model - self._device_firmware = self._api.information.version_string - self._device_type = ( - volume["device_type"] - .replace("_", " ") - .replace("raid", "RAID") - .replace("shr", "SHR") - ) - elif "disk" in description.key: - disk = self._api.storage.get_disk(self._device_id) - self._device_name = disk["name"] - self._device_manufacturer = disk["vendor"] - self._device_model = disk["model"].strip() - self._device_firmware = disk["firm"] - self._device_type = disk["diskType"] - self._name = ( - f"{self._api.network.hostname} {self._device_name} {description.name}" - ) - self._attr_unique_id += f"_{self._device_id}" - - @property - def available(self) -> bool: - """Return True if entity is available.""" - return self._api.storage # type: ignore [no-any-return] - - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - return DeviceInfo( - identifiers={(DOMAIN, f"{self._api.information.serial}_{self._device_id}")}, - name=f"Synology NAS ({self._device_name} - {self._device_type})", - manufacturer=self._device_manufacturer, - model=self._device_model, - sw_version=self._device_firmware, - via_device=(DOMAIN, self._api.information.serial), - configuration_url=self._api.config_url, - ) diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 7f0704790e1..a5c96575307 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -2,24 +2,77 @@ from __future__ import annotations from collections.abc import Mapping +from dataclasses import dataclass from typing import Any -from homeassistant.components.binary_sensor import BinarySensorEntity +from synology_dsm.api.core.security import SynoCoreSecurity +from synology_dsm.api.core.upgrade import SynoCoreUpgrade +from synology_dsm.api.storage.storage import SynoStorage + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DISKS from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMBaseEntity, SynologyDSMDeviceEntity -from .const import ( - COORDINATOR_CENTRAL, - DOMAIN, - SECURITY_BINARY_SENSORS, - STORAGE_DISK_BINARY_SENSORS, - SYNO_API, - UPGRADE_BINARY_SENSORS, - SynologyDSMBinarySensorEntityDescription, +from . import SynoApi +from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API +from .entity import ( + SynologyDSMBaseEntity, + SynologyDSMDeviceEntity, + SynologyDSMEntityDescription, +) + + +@dataclass +class SynologyDSMBinarySensorEntityDescription( + BinarySensorEntityDescription, SynologyDSMEntityDescription +): + """Describes Synology DSM binary sensor entity.""" + + +UPGRADE_BINARY_SENSORS: tuple[SynologyDSMBinarySensorEntityDescription, ...] = ( + SynologyDSMBinarySensorEntityDescription( + # Deprecated, scheduled to be removed in 2022.6 (#68664) + api_key=SynoCoreUpgrade.API_KEY, + key="update_available", + name="Update Available", + entity_registry_enabled_default=False, + device_class=BinarySensorDeviceClass.UPDATE, + entity_category=EntityCategory.DIAGNOSTIC, + ), +) + +SECURITY_BINARY_SENSORS: tuple[SynologyDSMBinarySensorEntityDescription, ...] = ( + SynologyDSMBinarySensorEntityDescription( + api_key=SynoCoreSecurity.API_KEY, + key="status", + name="Security Status", + device_class=BinarySensorDeviceClass.SAFETY, + ), +) + +STORAGE_DISK_BINARY_SENSORS: tuple[SynologyDSMBinarySensorEntityDescription, ...] = ( + SynologyDSMBinarySensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="disk_exceed_bad_sector_thr", + name="Exceeded Max Bad Sectors", + device_class=BinarySensorDeviceClass.SAFETY, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SynologyDSMBinarySensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="disk_below_remain_life_thr", + name="Below Min Remaining Life", + device_class=BinarySensorDeviceClass.SAFETY, + entity_category=EntityCategory.DIAGNOSTIC, + ), ) diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index 0999ef858c6..c6d44d8883d 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -11,9 +11,9 @@ from synology_dsm.exceptions import ( ) from homeassistant.components.camera import ( - SUPPORT_STREAM, Camera, CameraEntityDescription, + CameraEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -21,15 +21,15 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMBaseEntity +from . import SynoApi from .const import ( CONF_SNAPSHOT_QUALITY, COORDINATOR_CAMERAS, DEFAULT_SNAPSHOT_QUALITY, DOMAIN, SYNO_API, - SynologyDSMEntityDescription, ) +from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription _LOGGER = logging.getLogger(__name__) @@ -67,6 +67,7 @@ async def async_setup_entry( class SynoDSMCamera(SynologyDSMBaseEntity, Camera): """Representation a Synology camera.""" + _attr_supported_features = CameraEntityFeature.STREAM coordinator: DataUpdateCoordinator[dict[str, dict[str, SynoCamera]]] entity_description: SynologyDSMCameraEntityDescription @@ -119,11 +120,6 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera): """Return the availability of the camera.""" return self.camera_data.is_enabled and self.coordinator.last_update_success - @property - def supported_features(self) -> int: - """Return supported features of this camera.""" - return SUPPORT_STREAM - @property def is_recording(self) -> bool: """Return true if the device is recording.""" diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 78f1a1b916d..1b4e5f0bb36 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -1,37 +1,12 @@ """Constants for Synology DSM.""" from __future__ import annotations -from dataclasses import dataclass - -from synology_dsm.api.core.security import SynoCoreSecurity -from synology_dsm.api.core.upgrade import SynoCoreUpgrade -from synology_dsm.api.core.utilization import SynoCoreUtilization -from synology_dsm.api.dsm.information import SynoDSMInformation -from synology_dsm.api.storage.storage import SynoStorage -from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntityDescription, -) -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.components.switch import SwitchEntityDescription -from homeassistant.const import ( - DATA_MEGABYTES, - DATA_RATE_KILOBYTES_PER_SECOND, - DATA_TERABYTES, - PERCENTAGE, - TEMP_CELSIUS, - Platform, -) -from homeassistant.helpers.entity import EntityCategory, EntityDescription +from homeassistant.const import Platform DOMAIN = "synology_dsm" +ATTRIBUTION = "Data provided by Synology" PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, @@ -75,315 +50,3 @@ SERVICES = [ SERVICE_REBOOT, SERVICE_SHUTDOWN, ] - - -@dataclass -class SynologyDSMRequiredKeysMixin: - """Mixin for required keys.""" - - api_key: str - - -@dataclass -class SynologyDSMEntityDescription(EntityDescription, SynologyDSMRequiredKeysMixin): - """Generic Synology DSM entity description.""" - - -@dataclass -class SynologyDSMBinarySensorEntityDescription( - BinarySensorEntityDescription, SynologyDSMEntityDescription -): - """Describes Synology DSM binary sensor entity.""" - - -@dataclass -class SynologyDSMSensorEntityDescription( - SensorEntityDescription, SynologyDSMEntityDescription -): - """Describes Synology DSM sensor entity.""" - - -@dataclass -class SynologyDSMSwitchEntityDescription( - SwitchEntityDescription, SynologyDSMEntityDescription -): - """Describes Synology DSM switch entity.""" - - -# Binary sensors -UPGRADE_BINARY_SENSORS: tuple[SynologyDSMBinarySensorEntityDescription, ...] = ( - SynologyDSMBinarySensorEntityDescription( - # Deprecated, scheduled to be removed in 2022.6 (#68664) - api_key=SynoCoreUpgrade.API_KEY, - key="update_available", - name="Update Available", - entity_registry_enabled_default=False, - device_class=BinarySensorDeviceClass.UPDATE, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) - -SECURITY_BINARY_SENSORS: tuple[SynologyDSMBinarySensorEntityDescription, ...] = ( - SynologyDSMBinarySensorEntityDescription( - api_key=SynoCoreSecurity.API_KEY, - key="status", - name="Security Status", - device_class=BinarySensorDeviceClass.SAFETY, - ), -) - -STORAGE_DISK_BINARY_SENSORS: tuple[SynologyDSMBinarySensorEntityDescription, ...] = ( - SynologyDSMBinarySensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="disk_exceed_bad_sector_thr", - name="Exceeded Max Bad Sectors", - device_class=BinarySensorDeviceClass.SAFETY, - entity_category=EntityCategory.DIAGNOSTIC, - ), - SynologyDSMBinarySensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="disk_below_remain_life_thr", - name="Below Min Remaining Life", - device_class=BinarySensorDeviceClass.SAFETY, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) - -# Sensors -UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_other_load", - name="CPU Utilization (Other)", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_user_load", - name="CPU Utilization (User)", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_system_load", - name="CPU Utilization (System)", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_total_load", - name="CPU Utilization (Total)", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_1min_load", - name="CPU Load Average (1 min)", - native_unit_of_measurement=ENTITY_UNIT_LOAD, - icon="mdi:chip", - entity_registry_enabled_default=False, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_5min_load", - name="CPU Load Average (5 min)", - native_unit_of_measurement=ENTITY_UNIT_LOAD, - icon="mdi:chip", - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="cpu_15min_load", - name="CPU Load Average (15 min)", - native_unit_of_measurement=ENTITY_UNIT_LOAD, - icon="mdi:chip", - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_real_usage", - name="Memory Usage (Real)", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_size", - name="Memory Size", - native_unit_of_measurement=DATA_MEGABYTES, - icon="mdi:memory", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_cached", - name="Memory Cached", - native_unit_of_measurement=DATA_MEGABYTES, - icon="mdi:memory", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_available_swap", - name="Memory Available (Swap)", - native_unit_of_measurement=DATA_MEGABYTES, - icon="mdi:memory", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_available_real", - name="Memory Available (Real)", - native_unit_of_measurement=DATA_MEGABYTES, - icon="mdi:memory", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_total_swap", - name="Memory Total (Swap)", - native_unit_of_measurement=DATA_MEGABYTES, - icon="mdi:memory", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="memory_total_real", - name="Memory Total (Real)", - native_unit_of_measurement=DATA_MEGABYTES, - icon="mdi:memory", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="network_up", - name="Upload Throughput", - native_unit_of_measurement=DATA_RATE_KILOBYTES_PER_SECOND, - icon="mdi:upload", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoCoreUtilization.API_KEY, - key="network_down", - name="Download Throughput", - native_unit_of_measurement=DATA_RATE_KILOBYTES_PER_SECOND, - icon="mdi:download", - state_class=SensorStateClass.MEASUREMENT, - ), -) -STORAGE_VOL_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="volume_status", - name="Status", - icon="mdi:checkbox-marked-circle-outline", - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="volume_size_total", - name="Total Size", - native_unit_of_measurement=DATA_TERABYTES, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="volume_size_used", - name="Used Space", - native_unit_of_measurement=DATA_TERABYTES, - icon="mdi:chart-pie", - state_class=SensorStateClass.MEASUREMENT, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="volume_percentage_used", - name="Volume Used", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chart-pie", - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="volume_disk_temp_avg", - name="Average Disk Temp", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - entity_category=EntityCategory.DIAGNOSTIC, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="volume_disk_temp_max", - name="Maximum Disk Temp", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - entity_registry_enabled_default=False, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) -STORAGE_DISK_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="disk_smart_status", - name="Status (Smart)", - icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=False, - entity_category=EntityCategory.DIAGNOSTIC, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="disk_status", - name="Status", - icon="mdi:checkbox-marked-circle-outline", - entity_category=EntityCategory.DIAGNOSTIC, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoStorage.API_KEY, - key="disk_temp", - name="Temperature", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) - -INFORMATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( - SynologyDSMSensorEntityDescription( - api_key=SynoDSMInformation.API_KEY, - key="temperature", - name="Temperature", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - entity_category=EntityCategory.DIAGNOSTIC, - ), - SynologyDSMSensorEntityDescription( - api_key=SynoDSMInformation.API_KEY, - key="uptime", - name="Last Boot", - device_class=SensorDeviceClass.TIMESTAMP, - entity_registry_enabled_default=False, - entity_category=EntityCategory.DIAGNOSTIC, - ), -) - -# Switch -SURVEILLANCE_SWITCH: tuple[SynologyDSMSwitchEntityDescription, ...] = ( - SynologyDSMSwitchEntityDescription( - api_key=SynoSurveillanceStation.HOME_MODE_API_KEY, - key="home_mode", - name="Home Mode", - icon="mdi:home-account", - ), -) diff --git a/homeassistant/components/synology_dsm/entity.py b/homeassistant/components/synology_dsm/entity.py new file mode 100644 index 00000000000..1404d933020 --- /dev/null +++ b/homeassistant/components/synology_dsm/entity.py @@ -0,0 +1,122 @@ +"""Entities for Synology DSM.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +from .common import SynoApi +from .const import ATTRIBUTION, DOMAIN + + +@dataclass +class SynologyDSMRequiredKeysMixin: + """Mixin for required keys.""" + + api_key: str + + +@dataclass +class SynologyDSMEntityDescription(EntityDescription, SynologyDSMRequiredKeysMixin): + """Generic Synology DSM entity description.""" + + +class SynologyDSMBaseEntity( + CoordinatorEntity[DataUpdateCoordinator[dict[str, dict[str, Any]]]] +): + """Representation of a Synology NAS entry.""" + + entity_description: SynologyDSMEntityDescription + unique_id: str + _attr_attribution = ATTRIBUTION + + def __init__( + self, + api: SynoApi, + coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]], + description: SynologyDSMEntityDescription, + ) -> None: + """Initialize the Synology DSM entity.""" + super().__init__(coordinator) + self.entity_description = description + + self._api = api + self._attr_name = f"{api.network.hostname} {description.name}" + self._attr_unique_id: str = ( + f"{api.information.serial}_{description.api_key}:{description.key}" + ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self._api.information.serial)}, + name=self._api.network.hostname, + manufacturer="Synology", + model=self._api.information.model, + sw_version=self._api.information.version_string, + configuration_url=self._api.config_url, + ) + + async def async_added_to_hass(self) -> None: + """Register entity for updates from API.""" + self.async_on_remove( + self._api.subscribe(self.entity_description.api_key, self.unique_id) + ) + await super().async_added_to_hass() + + +class SynologyDSMDeviceEntity(SynologyDSMBaseEntity): + """Representation of a Synology NAS disk or volume entry.""" + + def __init__( + self, + api: SynoApi, + coordinator: DataUpdateCoordinator[dict[str, dict[str, Any]]], + description: SynologyDSMEntityDescription, + device_id: str | None = None, + ) -> None: + """Initialize the Synology DSM disk or volume entity.""" + super().__init__(api, coordinator, description) + self._device_id = device_id + self._device_name: str | None = None + self._device_manufacturer: str | None = None + self._device_model: str | None = None + self._device_firmware: str | None = None + self._device_type = None + + if "volume" in description.key: + volume = self._api.storage.get_volume(self._device_id) + # Volume does not have a name + self._device_name = volume["id"].replace("_", " ").capitalize() + self._device_manufacturer = "Synology" + self._device_model = self._api.information.model + self._device_firmware = self._api.information.version_string + self._device_type = ( + volume["device_type"] + .replace("_", " ") + .replace("raid", "RAID") + .replace("shr", "SHR") + ) + elif "disk" in description.key: + disk = self._api.storage.get_disk(self._device_id) + self._device_name = disk["name"] + self._device_manufacturer = disk["vendor"] + self._device_model = disk["model"].strip() + self._device_firmware = disk["firm"] + self._device_type = disk["diskType"] + + self._attr_name = ( + f"{self._api.network.hostname} ({self._device_name}) {description.name}" + ) + self._attr_unique_id += f"_{self._device_id}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{self._api.information.serial}_{self._device_id}")}, + name=f"{self._api.network.hostname} ({self._device_name})", + manufacturer=self._device_manufacturer, + model=self._device_model, + sw_version=self._device_firmware, + via_device=(DOMAIN, self._api.information.serial), + configuration_url=self._api.config_url, + ) diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 18014de8c7a..6015dc689b7 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -1,34 +1,277 @@ """Support for Synology DSM sensors.""" from __future__ import annotations +from dataclasses import dataclass from datetime import datetime, timedelta from typing import Any -from homeassistant.components.sensor import SensorEntity +from synology_dsm.api.core.utilization import SynoCoreUtilization +from synology_dsm.api.dsm.information import SynoDSMInformation +from synology_dsm.api.storage.storage import SynoStorage + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DISKS, DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, DATA_TERABYTES, + PERCENTAGE, + TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.dt import utcnow -from . import SynoApi, SynologyDSMBaseEntity, SynologyDSMDeviceEntity -from .const import ( - CONF_VOLUMES, - COORDINATOR_CENTRAL, - DOMAIN, - ENTITY_UNIT_LOAD, - INFORMATION_SENSORS, - STORAGE_DISK_SENSORS, - STORAGE_VOL_SENSORS, - SYNO_API, - UTILISATION_SENSORS, - SynologyDSMSensorEntityDescription, +from . import SynoApi +from .const import CONF_VOLUMES, COORDINATOR_CENTRAL, DOMAIN, ENTITY_UNIT_LOAD, SYNO_API +from .entity import ( + SynologyDSMBaseEntity, + SynologyDSMDeviceEntity, + SynologyDSMEntityDescription, +) + + +@dataclass +class SynologyDSMSensorEntityDescription( + SensorEntityDescription, SynologyDSMEntityDescription +): + """Describes Synology DSM sensor entity.""" + + +UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_other_load", + name="CPU Utilization (Other)", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chip", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_user_load", + name="CPU Utilization (User)", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chip", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_system_load", + name="CPU Utilization (System)", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chip", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_total_load", + name="CPU Utilization (Total)", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chip", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_1min_load", + name="CPU Load Average (1 min)", + native_unit_of_measurement=ENTITY_UNIT_LOAD, + icon="mdi:chip", + entity_registry_enabled_default=False, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_5min_load", + name="CPU Load Average (5 min)", + native_unit_of_measurement=ENTITY_UNIT_LOAD, + icon="mdi:chip", + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="cpu_15min_load", + name="CPU Load Average (15 min)", + native_unit_of_measurement=ENTITY_UNIT_LOAD, + icon="mdi:chip", + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_real_usage", + name="Memory Usage (Real)", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:memory", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_size", + name="Memory Size", + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:memory", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_cached", + name="Memory Cached", + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:memory", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_available_swap", + name="Memory Available (Swap)", + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:memory", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_available_real", + name="Memory Available (Real)", + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:memory", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_total_swap", + name="Memory Total (Swap)", + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:memory", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="memory_total_real", + name="Memory Total (Real)", + native_unit_of_measurement=DATA_MEGABYTES, + icon="mdi:memory", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="network_up", + name="Upload Throughput", + native_unit_of_measurement=DATA_RATE_KILOBYTES_PER_SECOND, + icon="mdi:upload", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoCoreUtilization.API_KEY, + key="network_down", + name="Download Throughput", + native_unit_of_measurement=DATA_RATE_KILOBYTES_PER_SECOND, + icon="mdi:download", + state_class=SensorStateClass.MEASUREMENT, + ), +) +STORAGE_VOL_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="volume_status", + name="Status", + icon="mdi:checkbox-marked-circle-outline", + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="volume_size_total", + name="Total Size", + native_unit_of_measurement=DATA_TERABYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="volume_size_used", + name="Used Space", + native_unit_of_measurement=DATA_TERABYTES, + icon="mdi:chart-pie", + state_class=SensorStateClass.MEASUREMENT, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="volume_percentage_used", + name="Volume Used", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chart-pie", + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="volume_disk_temp_avg", + name="Average Disk Temp", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="volume_disk_temp_max", + name="Maximum Disk Temp", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), +) +STORAGE_DISK_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="disk_smart_status", + name="Status (Smart)", + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="disk_status", + name="Status", + icon="mdi:checkbox-marked-circle-outline", + entity_category=EntityCategory.DIAGNOSTIC, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoStorage.API_KEY, + key="disk_temp", + name="Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), +) + +INFORMATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( + SynologyDSMSensorEntityDescription( + api_key=SynoDSMInformation.API_KEY, + key="temperature", + name="Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SynologyDSMSensorEntityDescription( + api_key=SynoDSMInformation.API_KEY, + key="uptime", + name="Last Boot", + device_class=SensorDeviceClass.TIMESTAMP, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), ) diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 3b3b801fcf7..eb61b8334ca 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -1,30 +1,43 @@ """Support for Synology DSM switch.""" from __future__ import annotations +from dataclasses import dataclass import logging from typing import Any from synology_dsm.api.surveillance_station import SynoSurveillanceStation -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMBaseEntity -from .const import ( - COORDINATOR_SWITCHES, - DOMAIN, - SURVEILLANCE_SWITCH, - SYNO_API, - SynologyDSMSwitchEntityDescription, -) +from . import SynoApi +from .const import COORDINATOR_SWITCHES, DOMAIN, SYNO_API +from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription _LOGGER = logging.getLogger(__name__) +@dataclass +class SynologyDSMSwitchEntityDescription( + SwitchEntityDescription, SynologyDSMEntityDescription +): + """Describes Synology DSM switch entity.""" + + +SURVEILLANCE_SWITCH: tuple[SynologyDSMSwitchEntityDescription, ...] = ( + SynologyDSMSwitchEntityDescription( + api_key=SynoSurveillanceStation.HOME_MODE_API_KEY, + key="home_mode", + name="Home Mode", + icon="mdi:home-account", + ), +) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: @@ -71,6 +84,10 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, SwitchEntity): super().__init__(api, coordinator, description) self._version = version + self._attr_name = ( + f"{self._api.network.hostname} Surveillance Station {description.name}" + ) + @property def is_on(self) -> bool: """Return the state.""" @@ -113,7 +130,7 @@ class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, SwitchEntity): f"{self._api.information.serial}_{SynoSurveillanceStation.INFO_API_KEY}", ) }, - name="Surveillance Station", + name=f"{self._api.network.hostname} Surveillance Station", manufacturer="Synology", model=self._api.information.model, sw_version=self._version, diff --git a/homeassistant/components/synology_dsm/update.py b/homeassistant/components/synology_dsm/update.py index 836468d6f50..48b3eeca2ed 100644 --- a/homeassistant/components/synology_dsm/update.py +++ b/homeassistant/components/synology_dsm/update.py @@ -13,8 +13,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import SynoApi, SynologyDSMBaseEntity -from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API, SynologyDSMEntityDescription +from . import SynoApi +from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API +from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription @dataclass diff --git a/homeassistant/components/syslog/manifest.json b/homeassistant/components/syslog/manifest.json index 35e039f9dd3..dd7528b08e6 100644 --- a/homeassistant/components/syslog/manifest.json +++ b/homeassistant/components/syslog/manifest.json @@ -2,6 +2,6 @@ "domain": "syslog", "name": "Syslog", "documentation": "https://www.home-assistant.io/integrations/syslog", - "codeowners": ["@fabaff"], + "codeowners": [], "iot_class": "local_push" } diff --git a/homeassistant/components/syslog/notify.py b/homeassistant/components/syslog/notify.py index 8ace605a0f5..5a5bac002e4 100644 --- a/homeassistant/components/syslog/notify.py +++ b/homeassistant/components/syslog/notify.py @@ -83,7 +83,7 @@ class SyslogNotificationService(BaseNotificationService): self._priority = priority def send_message(self, message="", **kwargs): - """Send a message to a user.""" + """Send a message to syslog.""" title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) diff --git a/homeassistant/components/system_bridge/translations/ca.json b/homeassistant/components/system_bridge/translations/ca.json index aac2e139db0..b17295d59e0 100644 --- a/homeassistant/components/system_bridge/translations/ca.json +++ b/homeassistant/components/system_bridge/translations/ca.json @@ -27,6 +27,5 @@ "description": "Introdueix les dades de connexi\u00f3." } } - }, - "title": "Enlla\u00e7 de sistema" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/de.json b/homeassistant/components/system_bridge/translations/de.json index dc85589dd32..0ea5a0abd3a 100644 --- a/homeassistant/components/system_bridge/translations/de.json +++ b/homeassistant/components/system_bridge/translations/de.json @@ -27,6 +27,5 @@ "description": "Bitte gib Verbindungsdaten ein." } } - }, - "title": "System-Br\u00fccke" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/el.json b/homeassistant/components/system_bridge/translations/el.json index 5576af325de..cc15d636f72 100644 --- a/homeassistant/components/system_bridge/translations/el.json +++ b/homeassistant/components/system_bridge/translations/el.json @@ -27,6 +27,5 @@ "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2." } } - }, - "title": "\u0393\u03ad\u03c6\u03c5\u03c1\u03b1 \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/en.json b/homeassistant/components/system_bridge/translations/en.json index 3ebcfc6959e..dc94dfe2ac6 100644 --- a/homeassistant/components/system_bridge/translations/en.json +++ b/homeassistant/components/system_bridge/translations/en.json @@ -27,6 +27,5 @@ "description": "Please enter your connection details." } } - }, - "title": "System Bridge" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/es.json b/homeassistant/components/system_bridge/translations/es.json index 2b7a859fcd1..d7fba5e7c32 100644 --- a/homeassistant/components/system_bridge/translations/es.json +++ b/homeassistant/components/system_bridge/translations/es.json @@ -27,6 +27,5 @@ "description": "Por favor, introduce tus datos de conexi\u00f3n." } } - }, - "title": "Pasarela del sistema" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/et.json b/homeassistant/components/system_bridge/translations/et.json index 3724895f147..69f32524c3b 100644 --- a/homeassistant/components/system_bridge/translations/et.json +++ b/homeassistant/components/system_bridge/translations/et.json @@ -27,6 +27,5 @@ "description": "Sisesta oma \u00fchenduse \u00fcksikasjad." } } - }, - "title": "S\u00fcsteemi sild" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/fr.json b/homeassistant/components/system_bridge/translations/fr.json index cbcad2d0330..e04dca1b751 100644 --- a/homeassistant/components/system_bridge/translations/fr.json +++ b/homeassistant/components/system_bridge/translations/fr.json @@ -27,6 +27,5 @@ "description": "Veuillez saisir vos informations de connexion." } } - }, - "title": "Pont syst\u00e8me" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/he.json b/homeassistant/components/system_bridge/translations/he.json index 5eb235ffb8f..ed758978398 100644 --- a/homeassistant/components/system_bridge/translations/he.json +++ b/homeassistant/components/system_bridge/translations/he.json @@ -27,6 +27,5 @@ "description": "\u05e0\u05d0 \u05d4\u05d6\u05df \u05d0\u05ea \u05e4\u05e8\u05d8\u05d9 \u05d4\u05d7\u05d9\u05d1\u05d5\u05e8 \u05e9\u05dc\u05da." } } - }, - "title": "\u05d2\u05e9\u05e8 \u05d4\u05de\u05e2\u05e8\u05db\u05ea" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/hu.json b/homeassistant/components/system_bridge/translations/hu.json index 8fdacd8718f..c570cd2e3c2 100644 --- a/homeassistant/components/system_bridge/translations/hu.json +++ b/homeassistant/components/system_bridge/translations/hu.json @@ -27,6 +27,5 @@ "description": "K\u00e9rj\u00fck, adja meg kapcsolati adatait." } } - }, - "title": "Rendszer h\u00edd" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/id.json b/homeassistant/components/system_bridge/translations/id.json index 9995253cbca..7cc07ebb2e1 100644 --- a/homeassistant/components/system_bridge/translations/id.json +++ b/homeassistant/components/system_bridge/translations/id.json @@ -27,6 +27,5 @@ "description": "Masukkan detail koneksi Anda." } } - }, - "title": "Jembatan Sistem" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/it.json b/homeassistant/components/system_bridge/translations/it.json index 2b6885edc44..7257d92ed7a 100644 --- a/homeassistant/components/system_bridge/translations/it.json +++ b/homeassistant/components/system_bridge/translations/it.json @@ -27,6 +27,5 @@ "description": "Inserisci i dettagli della tua connessione." } } - }, - "title": "Bridge di sistema" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/ja.json b/homeassistant/components/system_bridge/translations/ja.json index 48ee7fc2378..8358130359f 100644 --- a/homeassistant/components/system_bridge/translations/ja.json +++ b/homeassistant/components/system_bridge/translations/ja.json @@ -27,6 +27,5 @@ "description": "\u63a5\u7d9a\u306e\u8a73\u7d30\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002" } } - }, - "title": "\u30b7\u30b9\u30c6\u30e0\u30d6\u30ea\u30c3\u30b8" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/nl.json b/homeassistant/components/system_bridge/translations/nl.json index eefadebda35..c419123e50d 100644 --- a/homeassistant/components/system_bridge/translations/nl.json +++ b/homeassistant/components/system_bridge/translations/nl.json @@ -27,6 +27,5 @@ "description": "Voer uw verbindingsgegevens in." } } - }, - "title": "System Bridge" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/no.json b/homeassistant/components/system_bridge/translations/no.json index 5651dd7cb67..22ab7f91a57 100644 --- a/homeassistant/components/system_bridge/translations/no.json +++ b/homeassistant/components/system_bridge/translations/no.json @@ -27,6 +27,5 @@ "description": "Vennligst skriv inn tilkoblingsdetaljene dine." } } - }, - "title": "System Bridge" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/pl.json b/homeassistant/components/system_bridge/translations/pl.json index 0ae579842d6..6c3a1561b5d 100644 --- a/homeassistant/components/system_bridge/translations/pl.json +++ b/homeassistant/components/system_bridge/translations/pl.json @@ -27,6 +27,5 @@ "description": "Wprowad\u017a dane po\u0142\u0105czenia." } } - }, - "title": "Mostek System" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/pt-BR.json b/homeassistant/components/system_bridge/translations/pt-BR.json index ed1bcd01ded..6b86e19ab35 100644 --- a/homeassistant/components/system_bridge/translations/pt-BR.json +++ b/homeassistant/components/system_bridge/translations/pt-BR.json @@ -27,6 +27,5 @@ "description": "Por favor, insira os detalhes da sua conex\u00e3o." } } - }, - "title": "Ponte do sistema" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/ru.json b/homeassistant/components/system_bridge/translations/ru.json index be2cddfdc69..c18bd977413 100644 --- a/homeassistant/components/system_bridge/translations/ru.json +++ b/homeassistant/components/system_bridge/translations/ru.json @@ -27,6 +27,5 @@ "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f." } } - }, - "title": "System Bridge" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/tr.json b/homeassistant/components/system_bridge/translations/tr.json index 17e43680cca..15259c306a8 100644 --- a/homeassistant/components/system_bridge/translations/tr.json +++ b/homeassistant/components/system_bridge/translations/tr.json @@ -27,6 +27,5 @@ "description": "L\u00fctfen ba\u011flant\u0131 bilgilerinizi giriniz." } } - }, - "title": "System Bridge" + } } \ No newline at end of file diff --git a/homeassistant/components/system_bridge/translations/zh-Hant.json b/homeassistant/components/system_bridge/translations/zh-Hant.json index 5f45cc5dfcd..cca10d161b1 100644 --- a/homeassistant/components/system_bridge/translations/zh-Hant.json +++ b/homeassistant/components/system_bridge/translations/zh-Hant.json @@ -27,6 +27,5 @@ "description": "\u8acb\u8f38\u5165\u9023\u7dda\u8a0a\u606f\u3002" } } - }, - "title": "System Bridge" + } } \ No newline at end of file diff --git a/homeassistant/components/system_health/translations/pt-BR.json b/homeassistant/components/system_health/translations/pt-BR.json index eb6f66e8784..15f9774cb16 100644 --- a/homeassistant/components/system_health/translations/pt-BR.json +++ b/homeassistant/components/system_health/translations/pt-BR.json @@ -1,3 +1,3 @@ { - "title": "Integridade Do Sistema" + "title": "Integridade do Sistema" } \ No newline at end of file diff --git a/homeassistant/components/systemmonitor/manifest.json b/homeassistant/components/systemmonitor/manifest.json index e8a63c9c40a..b9fe86cbfa8 100644 --- a/homeassistant/components/systemmonitor/manifest.json +++ b/homeassistant/components/systemmonitor/manifest.json @@ -2,7 +2,7 @@ "domain": "systemmonitor", "name": "System Monitor", "documentation": "https://www.home-assistant.io/integrations/systemmonitor", - "requirements": ["psutil==5.8.0"], + "requirements": ["psutil==5.9.0"], "codeowners": [], "iot_class": "local_push", "loggers": ["psutil"] diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 3e6dd2cb130..87ca302f029 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -1,20 +1,18 @@ """Support for Tado thermostats.""" +from __future__ import annotations + import logging import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_OFF, FAN_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_HOME, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS @@ -128,7 +126,9 @@ def create_climate_entity(tado, name: str, zone_id: int, device_info: dict): _LOGGER.debug("Capabilities for zone %s: %s", zone_id, capabilities) zone_type = capabilities["type"] - support_flags = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + support_flags = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) supported_hvac_modes = [ TADO_TO_HA_HVAC_MODE_MAP[CONST_MODE_OFF], TADO_TO_HA_HVAC_MODE_MAP[CONST_MODE_SMART_SCHEDULE], @@ -145,12 +145,12 @@ def create_climate_entity(tado, name: str, zone_id: int, device_info: dict): supported_hvac_modes.append(TADO_TO_HA_HVAC_MODE_MAP[mode]) if capabilities[mode].get("swings"): - support_flags |= SUPPORT_SWING_MODE + support_flags |= ClimateEntityFeature.SWING_MODE if not capabilities[mode].get("fanSpeeds"): continue - support_flags |= SUPPORT_FAN_MODE + support_flags |= ClimateEntityFeature.FAN_MODE if supported_fan_modes: continue @@ -162,7 +162,7 @@ def create_climate_entity(tado, name: str, zone_id: int, device_info: dict): cool_temperatures = capabilities[CONST_MODE_COOL]["temperatures"] else: - supported_hvac_modes.append(HVAC_MODE_HEAT) + supported_hvac_modes.append(HVACMode.HEAT) if CONST_MODE_HEAT in capabilities: heat_temperatures = capabilities[CONST_MODE_HEAT]["temperatures"] @@ -262,7 +262,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): self._current_tado_fan_speed = CONST_FAN_OFF self._current_tado_hvac_mode = CONST_MODE_OFF - self._current_tado_hvac_action = CURRENT_HVAC_OFF + self._current_tado_hvac_action = HVACAction.OFF self._current_tado_swing_mode = TADO_SWING_OFF self._tado_zone_data = None @@ -310,15 +310,15 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): return self._tado_zone_data.current_temp @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ - return TADO_TO_HA_HVAC_MODE_MAP.get(self._current_tado_hvac_mode, HVAC_MODE_OFF) + return TADO_TO_HA_HVAC_MODE_MAP.get(self._current_tado_hvac_mode, HVACMode.OFF) @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. @@ -326,13 +326,13 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): return self._supported_hvac_modes @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. """ return TADO_HVAC_ACTION_TO_HA_HVAC_ACTION.get( - self._tado_zone_data.current_hvac_action, CURRENT_HVAC_OFF + self._tado_zone_data.current_hvac_action, HVACAction.OFF ) @property @@ -425,7 +425,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): new_hvac_mode = CONST_MODE_COOL if self._ac_device else CONST_MODE_HEAT self._control_hvac(target_temp=temperature, hvac_mode=new_hvac_mode) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" self._control_hvac(hvac_mode=HA_TO_TADO_HVAC_MODE_MAP[hvac_mode]) @@ -469,7 +469,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): @property def swing_modes(self): """Swing modes for the device.""" - if self._support_flags & SUPPORT_SWING_MODE: + if self._support_flags & ClimateEntityFeature.SWING_MODE: return [TADO_SWING_ON, TADO_SWING_OFF] return None @@ -621,10 +621,10 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): temperature_to_send = None fan_speed = None - if self._support_flags & SUPPORT_FAN_MODE: + if self._support_flags & ClimateEntityFeature.FAN_MODE: fan_speed = self._current_tado_fan_speed swing = None - if self._support_flags & SUPPORT_SWING_MODE: + if self._support_flags & ClimateEntityFeature.SWING_MODE: swing = self._current_tado_swing_mode self._tado.set_zone_overlay( diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 291d02cb403..b6b36444211 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -11,36 +11,25 @@ from PyTado.const import ( ) from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_DRY, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, FAN_AUTO, FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_HOME, + HVACAction, + HVACMode, ) TADO_HVAC_ACTION_TO_HA_HVAC_ACTION = { - CONST_HVAC_HEAT: CURRENT_HVAC_HEAT, - CONST_HVAC_DRY: CURRENT_HVAC_DRY, - CONST_HVAC_FAN: CURRENT_HVAC_FAN, - CONST_HVAC_COOL: CURRENT_HVAC_COOL, - CONST_HVAC_IDLE: CURRENT_HVAC_IDLE, - CONST_HVAC_OFF: CURRENT_HVAC_OFF, - CONST_HVAC_HOT_WATER: CURRENT_HVAC_HEAT, + CONST_HVAC_HEAT: HVACAction.HEATING, + CONST_HVAC_DRY: HVACAction.DRYING, + CONST_HVAC_FAN: HVACAction.FAN, + CONST_HVAC_COOL: HVACAction.COOLING, + CONST_HVAC_IDLE: HVACAction.IDLE, + CONST_HVAC_OFF: HVACAction.OFF, + CONST_HVAC_HOT_WATER: HVACAction.HEATING, } # Configuration @@ -121,10 +110,10 @@ ORDERED_KNOWN_TADO_MODES = [ ] TADO_MODES_TO_HA_CURRENT_HVAC_ACTION = { - CONST_MODE_HEAT: CURRENT_HVAC_HEAT, - CONST_MODE_DRY: CURRENT_HVAC_DRY, - CONST_MODE_FAN: CURRENT_HVAC_FAN, - CONST_MODE_COOL: CURRENT_HVAC_COOL, + CONST_MODE_HEAT: HVACAction.HEATING, + CONST_MODE_DRY: HVACAction.DRYING, + CONST_MODE_FAN: HVACAction.FAN, + CONST_MODE_COOL: HVACAction.COOLING, } # These modes will not allow a temp to be set @@ -137,13 +126,13 @@ TADO_MODES_WITH_NO_TEMP_SETTING = [CONST_MODE_AUTO, CONST_MODE_DRY, CONST_MODE_F # This runs the smart schedule # HA_TO_TADO_HVAC_MODE_MAP = { - HVAC_MODE_OFF: CONST_MODE_OFF, - HVAC_MODE_HEAT_COOL: CONST_MODE_AUTO, - HVAC_MODE_AUTO: CONST_MODE_SMART_SCHEDULE, - HVAC_MODE_HEAT: CONST_MODE_HEAT, - HVAC_MODE_COOL: CONST_MODE_COOL, - HVAC_MODE_DRY: CONST_MODE_DRY, - HVAC_MODE_FAN_ONLY: CONST_MODE_FAN, + HVACMode.OFF: CONST_MODE_OFF, + HVACMode.HEAT_COOL: CONST_MODE_AUTO, + HVACMode.AUTO: CONST_MODE_SMART_SCHEDULE, + HVACMode.HEAT: CONST_MODE_HEAT, + HVACMode.COOL: CONST_MODE_COOL, + HVACMode.DRY: CONST_MODE_DRY, + HVACMode.FAN_ONLY: CONST_MODE_FAN, } HA_TO_TADO_FAN_MODE_MAP = { diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index 7827564afa5..e208335033d 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -18,6 +18,7 @@ class TadoDeviceEntity(Entity): def device_info(self) -> DeviceInfo: """Return the device_info of the device.""" return DeviceInfo( + configuration_url=f"https://app.tado.com/en/main/settings/rooms-and-devices/device/{self.device_name}", identifiers={(DOMAIN, self.device_id)}, name=self.device_name, manufacturer=DEFAULT_NAME, @@ -45,6 +46,7 @@ class TadoHomeEntity(Entity): def device_info(self) -> DeviceInfo: """Return the device_info of the device.""" return DeviceInfo( + configuration_url="https://app.tado.com", identifiers={(DOMAIN, self.home_id)}, manufacturer=DEFAULT_NAME, model=TADO_HOME, @@ -66,6 +68,7 @@ class TadoZoneEntity(Entity): def device_info(self) -> DeviceInfo: """Return the device_info of the device.""" return DeviceInfo( + configuration_url=f"https://app.tado.com/en/main/home/zoneV2/{self.zone_id}", identifiers={(DOMAIN, self._device_zone_id)}, name=self.zone_name, manufacturer=DEFAULT_NAME, diff --git a/homeassistant/components/tado/translations/ca.json b/homeassistant/components/tado/translations/ca.json index 935b58483d7..c34d937cf7f 100644 --- a/homeassistant/components/tado/translations/ca.json +++ b/homeassistant/components/tado/translations/ca.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Activa el mode salvaguarda." + "fallback": "Tria el mode alternatiu." }, - "description": "El mode salvaguarda canviar\u00e0 a Planificaci\u00f3 Intel\u00b7ligent en el proper canvi de programaci\u00f3 o quan s'ajusti manualment una zona.", + "description": "El mode alternatiu et permet triar quan passar al mode alternatiu d'horari intel\u00b7ligent des de la teva zona manual. (NEXT_TIME_BLOCK:= Canvia al pr\u00f2xim canvi de l'horari intel\u00b7ligent; MANUAL:= No canvia fins que es cancel\u00b7la; TADO_DEFAULT:= Canvia en funci\u00f3 de la configuraci\u00f3 de l'app Tado).", "title": "Ajusta les opcions de Tado" } } diff --git a/homeassistant/components/tado/translations/de.json b/homeassistant/components/tado/translations/de.json index dec90a46aef..73f068949c3 100644 --- a/homeassistant/components/tado/translations/de.json +++ b/homeassistant/components/tado/translations/de.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Aktiviert den Fallback-Modus." + "fallback": "Aktiviere den Fallback-Modus." }, - "description": "Der Fallback-Modus wechselt beim n\u00e4chsten Zeitplanwechsel nach dem manuellen Anpassen einer Zone zu Smart Schedule.", + "description": "Mit dem Fallback-Modus kannst du festlegen, wann von deinem manuellen Zonen-Overlay auf den intelligenten Zeitplan umgeschaltet werden soll. (NEXT_TIME_BLOCK:= Wechsel bei der n\u00e4chsten Smart Schedule-\u00c4nderung; MANUAL:= Kein Wechsel, bis du abbrichst; TADO_DEFAULT:= Wechsel basierend auf deiner Einstellung in der Tado-App).", "title": "Passe die Tado-Optionen an." } } diff --git a/homeassistant/components/tado/translations/en.json b/homeassistant/components/tado/translations/en.json index 4e0df459437..30d090c77a1 100644 --- a/homeassistant/components/tado/translations/en.json +++ b/homeassistant/components/tado/translations/en.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Enable fallback mode." + "fallback": "Choose fallback mode." }, - "description": "Fallback mode will switch to Smart Schedule at next schedule switch after manually adjusting a zone.", + "description": "Fallback mode lets you choose when to fallback to Smart Schedule from your manual zone overlay. (NEXT_TIME_BLOCK:= Change at next Smart Schedule change; MANUAL:= Dont change until you cancel; TADO_DEFAULT:= Change based on your setting in Tado App).", "title": "Adjust Tado options." } } diff --git a/homeassistant/components/tado/translations/et.json b/homeassistant/components/tado/translations/et.json index 6a201b4fd2d..cb8cf14ad3b 100644 --- a/homeassistant/components/tado/translations/et.json +++ b/homeassistant/components/tado/translations/et.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Luba varure\u017eiim." + "fallback": "Vali varure\u017eiim." }, - "description": "P\u00e4rast tsooni k\u00e4sitsi reguleerimist l\u00fclitub varure\u017eiim j\u00e4rgmisel ajakava l\u00fclitil nutikasse ajakavasse.", + "description": "Varure\u017eiim v\u00f5imaldab valida millal naasta nutikale ajakavale k\u00e4sitsi tsooni \u00fclekattega. (NEXT_TIME_BLOCK:= Muuda j\u00e4rgmisel nutika ajakava muudatusel; MANUAL:= \u00c4ra muuda enne kui oled t\u00fchistanud; TADO_DEFAULT:= Muuda, l\u00e4htudes Tado rakenduse seadistustest).", "title": "Kohanda Tado suvandeid." } } diff --git a/homeassistant/components/tado/translations/fr.json b/homeassistant/components/tado/translations/fr.json index 2e10dc38a2e..a3ff3a9fabf 100644 --- a/homeassistant/components/tado/translations/fr.json +++ b/homeassistant/components/tado/translations/fr.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Activer le mode restreint." + "fallback": "Choisissez le mode de retour." }, - "description": "Le mode de repli passera au programme intelligent au prochain changement de programme apr\u00e8s avoir ajust\u00e9 manuellement une zone.", + "description": "Le mode de retour vous permet de choisir le moment du retour \u00e0 la programmation intelligente depuis votre zone de superposition manuelle. (NEXT_TIME_BLOCK\u00a0:= Modifier au prochain changement de programmation intelligente\u00a0; MANUAL\u00a0:= Ne pas modifier jusqu'\u00e0 annulation manuelle\u00a0; TADO_DEFAULT\u00a0:= Modifier en fonction de vos param\u00e8tres dans l'application Tado).", "title": "Ajustez les options de Tado." } } diff --git a/homeassistant/components/tado/translations/hu.json b/homeassistant/components/tado/translations/hu.json index dfde73ce428..8a96591e429 100644 --- a/homeassistant/components/tado/translations/hu.json +++ b/homeassistant/components/tado/translations/hu.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "A tartal\u00e9k m\u00f3d enged\u00e9lyez\u00e9se." + "fallback": "A tartal\u00e9k m\u00f3d be\u00e1ll\u00edt\u00e1sa." }, - "description": "A tartal\u00e9k m\u00f3d intelligens \u00fctemez\u00e9sre v\u00e1lt a k\u00f6vetkez\u0151 \u00fctemez\u00e9s kapcsol\u00f3n\u00e1l, miut\u00e1n manu\u00e1lisan be\u00e1ll\u00edtotta a z\u00f3n\u00e1t.", + "description": "A tartal\u00e9k m\u00f3dban kiv\u00e1laszthatja, hogy mikor t\u00e9rjen vissza Smart Schedule-ra a k\u00e9zi m\u00f3db\u00f3l. (NEXT_TIME_BLOCK:= V\u00e1ltoz\u00e1s a k\u00f6vetkez\u0151 intelligens \u00fctemez\u00e9s m\u00f3dos\u00edt\u00e1s\u00e1n\u00e1l; MANUAL:= Ne m\u00f3dos\u00edtsa, am\u00edg le nem mondja; TADO_DEFAULT:= V\u00e1ltoz\u00e1s a Tado App be\u00e1ll\u00edt\u00e1sai alapj\u00e1n).", "title": "\u00c1ll\u00edtsa be a Tado-t." } } diff --git a/homeassistant/components/tado/translations/id.json b/homeassistant/components/tado/translations/id.json index e6a00f9ee07..c3bd3d305e6 100644 --- a/homeassistant/components/tado/translations/id.json +++ b/homeassistant/components/tado/translations/id.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Aktifkan mode alternatif." + "fallback": "Pilih mode alternatif" }, - "description": "Mode fallback akan beralih ke Smart Schedule pada pergantian jadwal berikutnya setelah menyesuaikan zona secara manual.", + "description": "Mode alternatif memungkinkan Anda memilih kapan harus berubah ke Smart Schedule dari overlay zona manual Anda. (NEXT_TIME_BLOCK:= Ubah pada perubahan Smart Schedule berikutnya; MANUAL:= Jangan ubah sampai Anda membatalkan; TADO_DEFAULT:= Ubah berdasarkan pengaturan Anda di Aplikasi Tado).", "title": "Sesuaikan opsi Tado." } } diff --git a/homeassistant/components/tado/translations/it.json b/homeassistant/components/tado/translations/it.json index 34c9dfe597d..6c42534b32a 100644 --- a/homeassistant/components/tado/translations/it.json +++ b/homeassistant/components/tado/translations/it.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Abilita la modalit\u00e0 di ripiego." + "fallback": "Scegli la modalit\u00e0 di ripiego." }, - "description": "La modalit\u00e0 di ripiego passer\u00e0 a Smart Schedule al prossimo cambio di programma dopo aver regolato manualmente una zona.", + "description": "La modalit\u00e0 di ripiego ti consente di scegliere quando eseguire il ripiego alla pianificazione intelligente dalla sovrapposizione manuale delle zone. (NEXT_TIME_BLOCK:= Modifica alla successiva modifica della pianificazione intelligente; MANUAL:= Non modificare fino all'annullamento; TADO_DEFAULT:= Modifica in base alle tue impostazioni nell'applicazione Tado).", "title": "Regola le opzioni di Tado." } } diff --git a/homeassistant/components/tado/translations/nl.json b/homeassistant/components/tado/translations/nl.json index 3b6d914b71c..43b969b69eb 100644 --- a/homeassistant/components/tado/translations/nl.json +++ b/homeassistant/components/tado/translations/nl.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Schakel de terugvalmodus in." + "fallback": "Kies de terugvalmodus." }, - "description": "De fallback-modus schakelt over naar Smart Schedule bij de volgende schemaschakeling na het handmatig aanpassen van een zone.", + "description": "Met de terugvalmodus kunt u kiezen wanneer u wilt terugvallen op Smart Schedule vanuit uw handmatige zoneoverlay. (NEXT_TIME_BLOCK:= Verander bij de volgende wijziging van Smart Schedule; MANUAL:= Verander niet tot je annuleert; TADO_DEFAULT:= Verander op basis van je instelling in Tado App).", "title": "Pas Tado-opties aan." } } diff --git a/homeassistant/components/tado/translations/no.json b/homeassistant/components/tado/translations/no.json index 4b6db19e0af..f2ef0b7de6e 100644 --- a/homeassistant/components/tado/translations/no.json +++ b/homeassistant/components/tado/translations/no.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Aktiver tilbakefallsmodus." + "fallback": "Velg reservemodus." }, - "description": "Fallback-modus bytter til Smart Schedule ved neste planbryter etter manuell justering av en sone.", + "description": "Reservemodus lar deg velge n\u00e5r du vil falle tilbake til Smart Schedule fra det manuelle soneoverlegget ditt. (NEXT_TIME_BLOCK:= Endre ved neste Smart Schedule-endring; MANUAL:= Ikke endre f\u00f8r du avbryter; TADO_DEFAULT:= Endre basert p\u00e5 innstillingen din i Tado-appen).", "title": "Juster Tado-alternativene." } } diff --git a/homeassistant/components/tado/translations/pl.json b/homeassistant/components/tado/translations/pl.json index 4f109fc9e98..d88fb939530 100644 --- a/homeassistant/components/tado/translations/pl.json +++ b/homeassistant/components/tado/translations/pl.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "W\u0142\u0105cz tryb awaryjny." + "fallback": "Wybierz tryb awaryjny." }, - "description": "Tryb rezerwowy prze\u0142\u0105czy si\u0119 na inteligentny harmonogram przy nast\u0119pnym prze\u0142\u0105czeniu z harmonogramu po r\u0119cznym dostosowaniu strefy.", + "description": "Tryb awaryjny pozwala wybra\u0107, kiedy wr\u00f3ci\u0107 do inteligentnego harmonogramu z r\u0119cznej nak\u0142adki strefy. (NEXT_TIME_BLOCK:= Zmie\u0144 przy nast\u0119pnej zmianie inteligentnego harmonogramu; MANUAL:= Nie zmieniaj, dop\u00f3ki nie anulujesz; TADO_DEFAULT:= Zmie\u0144 na podstawie ustawie\u0144 w aplikacji Tado).", "title": "Dostosuj opcje Tado" } } diff --git a/homeassistant/components/tado/translations/pt-BR.json b/homeassistant/components/tado/translations/pt-BR.json index 68cf24fe5df..e0c11a94830 100644 --- a/homeassistant/components/tado/translations/pt-BR.json +++ b/homeassistant/components/tado/translations/pt-BR.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Ative o modo de fallback." + "fallback": "Escolha o modo de fallback." }, - "description": "O modo Fallback mudar\u00e1 para Smart Schedule na pr\u00f3xima troca de agendamento ap\u00f3s ajustar manualmente uma zona.", + "description": "O modo fallback permite que voc\u00ea escolha quando fazer fallback para o Smart Schedule a partir de sua sobreposi\u00e7\u00e3o de zona manual. (NEXT_TIME_BLOCK:= Altere na pr\u00f3xima altera\u00e7\u00e3o do Smart Schedule; MANUAL:= N\u00e3o altere at\u00e9 cancelar; TADO_DEFAULT:= Altere com base na sua configura\u00e7\u00e3o no Tado App).", "title": "Ajuste as op\u00e7\u00f5es do Tado." } } diff --git a/homeassistant/components/tado/translations/ru.json b/homeassistant/components/tado/translations/ru.json index 18e9ddff67b..af2e96ae5ef 100644 --- a/homeassistant/components/tado/translations/ru.json +++ b/homeassistant/components/tado/translations/ru.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c Fallback" + "fallback": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c." }, - "description": "\u0420\u0435\u0436\u0438\u043c Fallback \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0441\u044f \u043d\u0430 Smart Schedule \u043f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u043b\u0435 \u0440\u0443\u0447\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u043e\u043d\u044b.", + "description": "\u0420\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u0431\u0440\u0430\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u043a \u0438\u043d\u0442\u0435\u043b\u043b\u0435\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u0438\u0437 \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u043d\u0430\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0437\u043e\u043d\u044b. (NEXT_TIME_BLOCK:= \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u0441\u043c\u0430\u0440\u0442-\u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f; MANUAL:= \u041d\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c, \u043f\u043e\u043a\u0430 \u0412\u044b \u043d\u0435 \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u0435; TADO_DEFAULT:= \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0412\u0430\u0448\u0438\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 Tado).", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Tado" } } diff --git a/homeassistant/components/tado/translations/tr.json b/homeassistant/components/tado/translations/tr.json index 30fc86b8e6a..76e4c25eefb 100644 --- a/homeassistant/components/tado/translations/tr.json +++ b/homeassistant/components/tado/translations/tr.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "Geri d\u00f6n\u00fc\u015f modunu etkinle\u015ftirin." + "fallback": "Geri d\u00f6n\u00fc\u015f modunu se\u00e7in." }, - "description": "Geri d\u00f6n\u00fc\u015f modu, bir b\u00f6lgeyi manuel olarak ayarlad\u0131ktan sonra bir sonraki program anahtar\u0131nda Ak\u0131ll\u0131 Programa ge\u00e7ecektir.", + "description": "Geri d\u00f6n\u00fc\u015f modu, manuel b\u00f6lge yerle\u015fiminizden Ak\u0131ll\u0131 Programa ne zaman geri d\u00f6nece\u011finizi se\u00e7menizi sa\u011flar. (NEXT_TIME_BLOCK:= Sonraki Ak\u0131ll\u0131 Program de\u011fi\u015fikli\u011finde de\u011fi\u015ftirin; MANUAL:= \u0130ptal edene kadar de\u011fi\u015ftirmeyin; TADO_DEFAULT:= Tado Uygulamas\u0131ndaki ayar\u0131n\u0131za g\u00f6re de\u011fi\u015ftirin).", "title": "Tado se\u00e7eneklerini ayarlay\u0131n." } } diff --git a/homeassistant/components/tado/translations/zh-Hant.json b/homeassistant/components/tado/translations/zh-Hant.json index e7f1f41ce3b..794496d7994 100644 --- a/homeassistant/components/tado/translations/zh-Hant.json +++ b/homeassistant/components/tado/translations/zh-Hant.json @@ -23,9 +23,9 @@ "step": { "init": { "data": { - "fallback": "\u958b\u555f\u5f8c\u964d\u6a21\u5f0f" + "fallback": "\u9078\u64c7\u5f8c\u964d\u6a21\u5f0f\u3002" }, - "description": "\u5f8c\u964d\u6a21\u5f0f\u5c07\u6703\u65bc\u624b\u52d5\u8abf\u6574\u5340\u57df\u5f8c\uff0c\u4e0b\u4e00\u6b21\u898f\u5283\u5207\u63db\u6642\u3001\u5207\u63db\u5230\u667a\u80fd\u884c\u7a0b\u3002", + "description": "\u5f8c\u964d\u6a21\u5f0f\u8b93\u60a8\u9078\u64c7\u7576\u81ea\u624b\u52d5\u8abf\u6574\u5340\u57df\u5f8c\u3001\u5207\u63db\u5230\u667a\u80fd\u6a21\u5f0f\u3002\uff08NEXT_TIME_BLOCK:= \u65bc\u4e0b\u6b21\u667a\u80fd\u6a21\u5f0f\u8b8a\u66f4\u6642\u8b8a\u66f4; MANUAL:= \u76f4\u5230\u53d6\u6d88\u524d\u4e0d\u8981\u8b8a\u66f4; TADO_DEFAULT:= \u57fa\u65bc Tado App \u4e2d\u8a2d\u5b9a\u8b8a\u66f4)\u3002", "title": "\u8abf\u6574 Tado \u9078\u9805\u3002" } } diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index 8ae5326622c..a009fdc3b92 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -4,9 +4,8 @@ import logging import voluptuous as vol from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -48,8 +47,6 @@ WATER_HEATER_MAP_TADO = { CONST_MODE_OFF: MODE_OFF, } -SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE - SERVICE_WATER_HEATER_TIMER = "set_water_heater_timer" ATTR_TIME_PERIOD = "time_period" @@ -147,9 +144,9 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): self._target_temp = None - self._supported_features = SUPPORT_FLAGS_HEATER + self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE if self._supports_temperature_control: - self._supported_features |= SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE self._current_tado_hvac_mode = CONST_MODE_SMART_SCHEDULE self._overlay_mode = CONST_MODE_SMART_SCHEDULE @@ -169,11 +166,6 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): ) self._async_update_data() - @property - def supported_features(self): - """Return the list of supported features.""" - return self._supported_features - @property def name(self): """Return the name of the entity.""" diff --git a/homeassistant/components/tailscale/diagnostics.py b/homeassistant/components/tailscale/diagnostics.py index 3c3393adc42..0fd69a12825 100644 --- a/homeassistant/components/tailscale/diagnostics.py +++ b/homeassistant/components/tailscale/diagnostics.py @@ -23,7 +23,6 @@ TO_REDACT = { "name", "node_key", "user", - "user", } diff --git a/homeassistant/components/tailscale/translations/ca.json b/homeassistant/components/tailscale/translations/ca.json index d111da767f2..a339ce6a22b 100644 --- a/homeassistant/components/tailscale/translations/ca.json +++ b/homeassistant/components/tailscale/translations/ca.json @@ -19,7 +19,7 @@ "api_key": "Clau API", "tailnet": "Tailnet" }, - "description": "Per autenticar-te amb Tailscale, has de crear una clau API a https://login.tailscale.com/admin/settings/authkeys. \n\nLa Tailnet \u00e9s el nom de la teva xarxa Tailscale. La pots trobar a l'extrem superior esquerre del tauler d'administraci\u00f3 de Tailscale (al costat del logotip de Tailscale)." + "description": "Aquesta integraci\u00f3 monitoritza la teva xarxa Tailscale, per\u00f2 NO fa que Home Assistant sigui accessible a trav\u00e9s de la VPN de Tailscale.\n\nPer autenticar-te amb Tailscale, has de crear una clau API a {authkeys_url}. \n\nTailnet \u00e9s el nom de la teva xarxa Tailscale. La pots trobar a l'extrem superior esquerre del tauler d'administraci\u00f3 de Tailscale (al costat del logotip de Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/de.json b/homeassistant/components/tailscale/translations/de.json index 9fbcceaa674..fca05d46a6e 100644 --- a/homeassistant/components/tailscale/translations/de.json +++ b/homeassistant/components/tailscale/translations/de.json @@ -19,7 +19,7 @@ "api_key": "API-Schl\u00fcssel", "tailnet": "Tailnet" }, - "description": "Um sich bei Tailscale zu authentifizieren, musst du einen API-Schl\u00fcssel unter https://login.tailscale.com/admin/settings/authkeys erstellen.\n\nEin Tailnet ist der Name Ihres Tailscale-Netzwerks. Sie finden ihn in der linken oberen Ecke des Tailscale Admin Panels (neben dem Tailscale Logo)." + "description": "Diese Integration \u00fcberwacht dein Tailscale-Netzwerk, sie macht deinen Home Assistant **nicht** \u00fcber Tailscale VPN zug\u00e4nglich. \n\nUm sich bei Tailscale zu authentifizieren, musst du einen API-Schl\u00fcssel unter {authkeys_url} erstellen.\n\nEin Tailnet ist der Name deines Tailscale-Netzwerks. Du findest es oben links im Tailscale Admin Panel (neben dem Tailscale Logo)." } } } diff --git a/homeassistant/components/tailscale/translations/et.json b/homeassistant/components/tailscale/translations/et.json index d542c930ecd..1f8a677f2af 100644 --- a/homeassistant/components/tailscale/translations/et.json +++ b/homeassistant/components/tailscale/translations/et.json @@ -19,7 +19,7 @@ "api_key": "API v\u00f5ti", "tailnet": "Tailnet" }, - "description": "Tailscale'iga autentimiseks pead looma API v\u00f5tme aadressil https://login.tailscale.com/admin/settings/authkeys. \n\n Tailnet on Tailscale v\u00f5rgu nimi. Leiad selle Tailscale halduspaneeli vasakus \u00fclanurgas (Tailscale logo k\u00f5rval)." + "description": "See sidumine j\u00e4lgib Tailscale v\u00f5rku, see **EI TEE** seda Home Assistantile juurdep\u00e4\u00e4setavaks Tailscale VPN-i kaudu. \n\n Tailscale'iga autentimiseks pead looma API v\u00f5tme aadressil {authkeys_url} . \n\n Tailnet on Tailscale v\u00f5rgu nimi. Leiadselle Tailscale halduspaneeli vasakus \u00fclanurgas (Tailscale logo k\u00f5rval)." } } } diff --git a/homeassistant/components/tailscale/translations/fr.json b/homeassistant/components/tailscale/translations/fr.json index 1163ec2d151..c879c2a72e7 100644 --- a/homeassistant/components/tailscale/translations/fr.json +++ b/homeassistant/components/tailscale/translations/fr.json @@ -19,7 +19,7 @@ "api_key": "Cl\u00e9 d'API", "tailnet": "Tailnet" }, - "description": "Pour vous authentifier avec Tailscale, vous devrez cr\u00e9er une cl\u00e9 API sur https://login.tailscale.com/admin/settings/authkeys. \n\n Un Tailnet est le nom de votre r\u00e9seau Tailscale. Vous pouvez le trouver dans le coin sup\u00e9rieur gauche du panneau d'administration Tailscale (\u00e0 c\u00f4t\u00e9 du logo Tailscale)." + "description": "Cette int\u00e9gration permet de surveiller votre r\u00e9seau Tailscale\u00a0; elle **NE REND PAS** votre instance Home Assistant accessible via le VPN Tailscale.\n\nAfin de vous authentifier aupr\u00e8s de Tailscale, vous devez cr\u00e9er une cl\u00e9 d'API sur {authkeys_url}.\n\nUn Tailnet est le nom de votre r\u00e9seau Tailscale. Vous pouvez le trouver dans le coin sup\u00e9rieur gauche du panneau d'administration Tailscale (\u00e0 c\u00f4t\u00e9 du logo Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/hu.json b/homeassistant/components/tailscale/translations/hu.json index ec727cbc00f..bbc41483704 100644 --- a/homeassistant/components/tailscale/translations/hu.json +++ b/homeassistant/components/tailscale/translations/hu.json @@ -19,7 +19,7 @@ "api_key": "API kulcs", "tailnet": "Tailnet" }, - "description": "A Tailscale-rel val\u00f3 hiteles\u00edt\u00e9shez l\u00e9tre kell hoznia egy API-kulcsot a https://login.tailscale.com/admin/settings/authkeys oldalon.\n\nTailnet az \u00f6n tailscale h\u00e1l\u00f3zat\u00e1nak neve. Megtal\u00e1lhat\u00f3 a bal fels\u0151 sarokban a Tailscale Admin panelen (a Tailscale log\u00f3 mellett)." + "description": "Ez az integr\u00e1ci\u00f3 figyeli a Tailscale h\u00e1l\u00f3zat\u00e1t, \u00e9s **NEM** teszi el\u00e9rhet\u0151v\u00e9 az otthoni asszisztenst a Tailscale VPN-en kereszt\u00fcl. \n\nA Tailscale-n\u00e1l t\u00f6rt\u00e9n\u0151 hiteles\u00edt\u00e9shez l\u00e9tre kell hoznia egy API-kulcsot a {authkeys_url} c\u00edmen.\n\nA Tailnet az \u00d6n Tailscale h\u00e1l\u00f3zat\u00e1nak neve. Ezt a Tailscale Admin Panel bal fels\u0151 sark\u00e1ban tal\u00e1lja (a Tailscale log\u00f3 mellett)." } } } diff --git a/homeassistant/components/tailscale/translations/id.json b/homeassistant/components/tailscale/translations/id.json index d88a47fa82e..53b82c440aa 100644 --- a/homeassistant/components/tailscale/translations/id.json +++ b/homeassistant/components/tailscale/translations/id.json @@ -19,7 +19,7 @@ "api_key": "Kunci API", "tailnet": "Tailnet" }, - "description": "Untuk mengautentikasi dengan Tailscale, Anda harus membuat kunci API di https://login.tailscale.com/admin/settings/authkeys. \n\nTailnet adalah nama jaringan Tailscale Anda. Anda dapat menemukannya di pojok kiri atas di Panel Admin Tailscale (di samping logo Tailscale)." + "description": "Integrasi ini memantau jaringan Tailscale Anda, integrasi ini **TIDAK** membuat Home Assistant dapat diakses melalui Tailscale VPN. \n\nUntuk mengautentikasi dengan Tailscale, Anda harus membuat kunci API di {authkeys_url} . \n\nTailnet adalah nama jaringan Tailscale Anda. Anda dapat menemukannya di sudut kiri atas di Panel Admin Tailscale (di samping logo Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/it.json b/homeassistant/components/tailscale/translations/it.json index 0dfe90b8f2e..dba31833cf9 100644 --- a/homeassistant/components/tailscale/translations/it.json +++ b/homeassistant/components/tailscale/translations/it.json @@ -19,7 +19,7 @@ "api_key": "Chiave API", "tailnet": "Tailnet" }, - "description": "Per autenticarti con Tailscale dovrai creare una chiave API su https://login.tailscale.com/admin/settings/authkeys. \n\nUna Tailnet \u00e8 il nome della tua rete Tailscale. Puoi trovarlo nell'angolo in alto a sinistra nel pannello di amministrazione di Tailscale (accanto al logo Tailscale)." + "description": "Questa integrazione monitora la tua rete Tailscale, **NON** rende il tuo Home Assistant accessibile tramite Tailscale VPN. \n\nPer autenticarti con Tailscale dovrai creare una chiave API in {authkeys_url}. \n\nUn Tailnet \u00e8 il nome della tua rete Tailscale. Puoi trovarlo nell'angolo in alto a sinistra del pannello di amministrazione di Tailscale (accanto al logo di Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/nl.json b/homeassistant/components/tailscale/translations/nl.json index 5e46f4f0511..1a59a3ca029 100644 --- a/homeassistant/components/tailscale/translations/nl.json +++ b/homeassistant/components/tailscale/translations/nl.json @@ -19,7 +19,7 @@ "api_key": "API-sleutel", "tailnet": "Tailnet" }, - "description": "Om te authenticeren met Tailscale moet je een API-sleutel maken op https://login.tailscale.com/admin/settings/authkeys. \n\n Een Tailnet is de naam van uw Tailscale-netwerk. Je vindt het in de linkerbovenhoek in het Tailscale Admin Panel (naast het Tailscale-logo)." + "description": "Deze integratie controleert uw Tailscale netwerk, het maakt uw Home Assistant **NIET** toegankelijk via Tailscale VPN. \n\nOm te authenticeren met Tailscale moet u een API sleutel aanmaken op {authkeys_url}.\n\nEen Tailnet is de naam van uw Tailscale netwerk. U kunt het vinden in de linkerbovenhoek in het Tailscale Admin Panel (naast het Tailscale logo)." } } } diff --git a/homeassistant/components/tailscale/translations/no.json b/homeassistant/components/tailscale/translations/no.json index 627facd8f66..5c1ae4c6bc0 100644 --- a/homeassistant/components/tailscale/translations/no.json +++ b/homeassistant/components/tailscale/translations/no.json @@ -19,7 +19,7 @@ "api_key": "API-n\u00f8kkel", "tailnet": "Tailnet" }, - "description": "For \u00e5 autentisere med Tailscale m\u00e5 du opprette en API-n\u00f8kkel p\u00e5 https://login.tailscale.com/admin/settings/authkeys. \n\n Et Tailnet er navnet p\u00e5 Tailscale-nettverket ditt. Du finner den i \u00f8verste venstre hj\u00f8rne i Tailscale Admin Panel (ved siden av Tailscale-logoen)." + "description": "Denne integrasjonen overv\u00e5ker Tailscale-nettverket ditt, det ** GJ\u00d8R IKKE ** gj\u00f8r hjemmeassistenten din tilgjengelig via Tailscale VPN. \n\nHvis du vil godkjenne med Tailscale, m\u00e5 du opprette en API-n\u00f8kkel p\u00e5 {authkeys_url}.\n\nEt Tailnet er navnet p\u00e5 Tailscale-nettverket. Du finner den \u00f8verst til venstre i Tailscale Admin Panel (ved siden av Tailscale-logoen)." } } } diff --git a/homeassistant/components/tailscale/translations/pl.json b/homeassistant/components/tailscale/translations/pl.json index 579d8de83e1..ef5f1a4dabf 100644 --- a/homeassistant/components/tailscale/translations/pl.json +++ b/homeassistant/components/tailscale/translations/pl.json @@ -19,7 +19,7 @@ "api_key": "Klucz API", "tailnet": "Tailnet" }, - "description": "Aby uwierzytelni\u0107 si\u0119 w Tailscale, musisz utworzy\u0107 klucz API na stronie https://login.tailscale.com/admin/settings/authkeys.\n\nTailnet to nazwa Twojej sieci Tailscale. Mo\u017cna j\u0105 znale\u017a\u0107 w lewym g\u00f3rnym rogu w panelu administracyjnym Tailscale (obok loga Tailscale)." + "description": "Ta integracja monitoruje Twoj\u0105 sie\u0107 Tailscale, a **NIE SPRAWIA**, \u017ce Tw\u00f3j Home Assistant jest dost\u0119pny przez Tailscale VPN.\n\nAby uwierzytelni\u0107 si\u0119 w Tailscale, musisz utworzy\u0107 klucz API na stronie {authkeys_url}.\n\nTailnet to nazwa Twojej sieci Tailscale. Mo\u017cna j\u0105 znale\u017a\u0107 w lewym g\u00f3rnym rogu w panelu administracyjnym Tailscale (obok loga Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/pt-BR.json b/homeassistant/components/tailscale/translations/pt-BR.json index ddbdda9d5a6..edd55ea6e87 100644 --- a/homeassistant/components/tailscale/translations/pt-BR.json +++ b/homeassistant/components/tailscale/translations/pt-BR.json @@ -19,7 +19,7 @@ "api_key": "Chave da API", "tailnet": "Tailnet" }, - "description": "Para autenticar com o Tailscale, voc\u00ea precisar\u00e1 criar uma chave de API em https://login.tailscale.com/admin/settings/authkeys. \n\nTailnet \u00e9 o nome da sua rede Tailscale. Voc\u00ea pode encontr\u00e1-lo no canto superior esquerdo no painel de administra\u00e7\u00e3o do Tailscale (ao lado do logotipo do Tailscale)." + "description": "Esta integra\u00e7\u00e3o monitora sua rede Tailscale, **N\u00c3O** torna seu Home Assistant acess\u00edvel via Tailscale VPN. \n\n Para autenticar com o Tailscale, voc\u00ea precisar\u00e1 criar uma chave de API em {authkeys_url} . \n\n Um Tailnet \u00e9 o nome da sua rede Tailscale. Voc\u00ea pode encontr\u00e1-lo no canto superior esquerdo no painel de administra\u00e7\u00e3o do Tailscale (ao lado do logotipo do Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/ru.json b/homeassistant/components/tailscale/translations/ru.json index 1b97b0998e7..c05e3a09eac 100644 --- a/homeassistant/components/tailscale/translations/ru.json +++ b/homeassistant/components/tailscale/translations/ru.json @@ -19,7 +19,7 @@ "api_key": "\u041a\u043b\u044e\u0447 API", "tailnet": "Tailnet" }, - "description": "\u0414\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 https://login.tailscale.com/admin/settings/authkeys. \n\nTailnet \u2014 \u044d\u0442\u043e \u0438\u043c\u044f \u0412\u0430\u0448\u0435\u0439 \u0441\u0435\u0442\u0438 Tailscale. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0439\u0442\u0438 \u0435\u0433\u043e \u0432 \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u043b\u0435\u0432\u043e\u043c \u0443\u0433\u043b\u0443 \u043f\u0430\u043d\u0435\u043b\u0438 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 Tailscale (\u0440\u044f\u0434\u043e\u043c \u0441 \u043b\u043e\u0433\u043e\u0442\u0438\u043f\u043e\u043c Tailscale)." + "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u0412\u0430\u0448\u0443 \u0441\u0435\u0442\u044c Tailscale, \u043e\u043d\u0430 **\u041d\u0415** \u0434\u0435\u043b\u0430\u0435\u0442 \u0412\u0430\u0448 Home Assistant \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0447\u0435\u0440\u0435\u0437 Tailscale VPN. \n\n\u0414\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 {authkeys_url}. \n\nTailnet \u2014 \u044d\u0442\u043e \u0438\u043c\u044f \u0412\u0430\u0448\u0435\u0439 \u0441\u0435\u0442\u0438 Tailscale. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0439\u0442\u0438 \u0435\u0433\u043e \u0432 \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u043b\u0435\u0432\u043e\u043c \u0443\u0433\u043b\u0443 \u043f\u0430\u043d\u0435\u043b\u0438 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 Tailscale (\u0440\u044f\u0434\u043e\u043c \u0441 \u043b\u043e\u0433\u043e\u0442\u0438\u043f\u043e\u043c Tailscale)." } } } diff --git a/homeassistant/components/tailscale/translations/tr.json b/homeassistant/components/tailscale/translations/tr.json index 680acd65d70..784bda86b7d 100644 --- a/homeassistant/components/tailscale/translations/tr.json +++ b/homeassistant/components/tailscale/translations/tr.json @@ -19,7 +19,7 @@ "api_key": "API Anahtar\u0131", "tailnet": "Tailnet" }, - "description": "Tailscale ile kimlik do\u011frulamas\u0131 yapmak i\u00e7in https://login.tailscale.com/admin/settings/authkeys adresinde bir API anahtar\u0131 olu\u015fturman\u0131z gerekir. \n\n Kuyruk a\u011f\u0131, Kuyruk \u00f6l\u00e7e\u011fi a\u011f\u0131n\u0131z\u0131n ad\u0131d\u0131r. Bunu, Tailscale Y\u00f6netici Panelinin sol \u00fcst k\u00f6\u015fesinde (Tailscale logosunun yan\u0131nda) bulabilirsiniz." + "description": "Bu entegrasyon, Tailscale a\u011f\u0131n\u0131z\u0131 izler, ancak Ev Asistan\u0131n\u0131z\u0131 Tailscale VPN arac\u0131l\u0131\u011f\u0131yla eri\u015filebilir k\u0131lmaz. \n\n Tailscale ile kimlik do\u011frulamas\u0131 yapmak i\u00e7in {authkeys_url} adresinde bir API anahtar\u0131 olu\u015fturman\u0131z gerekir. \n\n Kuyruk a\u011f\u0131, Kuyruk \u00f6l\u00e7e\u011fi a\u011f\u0131n\u0131z\u0131n ad\u0131d\u0131r. Bunu, Tailscale Y\u00f6netici Panelinin sol \u00fcst k\u00f6\u015fesinde (Tailscale logosunun yan\u0131nda) bulabilirsiniz." } } } diff --git a/homeassistant/components/tailscale/translations/zh-Hant.json b/homeassistant/components/tailscale/translations/zh-Hant.json index 5ed5f1deb8f..316168fa67a 100644 --- a/homeassistant/components/tailscale/translations/zh-Hant.json +++ b/homeassistant/components/tailscale/translations/zh-Hant.json @@ -19,7 +19,7 @@ "api_key": "API \u91d1\u9470", "tailnet": "Tailnet" }, - "description": "\u6b32\u4f7f\u7528 Tailscale \u8a8d\u8b49\u3001\u5c07\u9700\u8981\u65bc https://login.tailscale.com/admin/settings/authkeys \u65b0\u589e\u4e00\u7d44 API \u91d1\u9470 \n\nTailnet \u70ba Tailscale \u7db2\u8def\u7684\u540d\u7a31\uff0c\u53ef\u4ee5\u65bc Tailscale \u7ba1\u7406\u9762\u677f\uff08Tailscale Logo \u65c1\uff09\u7684\u5de6\u4e0a\u65b9\u627e\u5230\u6b64\u8cc7\u8a0a\u3002" + "description": "\u6574\u5408\u5c07\u76e3\u63a7 Tailscale \u7db2\u8def\uff0c**\u4e26\u975e** \u8b93\u60a8\u7684 Home Assistant \u900f\u904e Tailscale VPN \u5b58\u53d6\u3002 \n\n\u6b32\u4f7f\u7528 Tailscale \u8a8d\u8b49\u3001\u5c07\u9700\u8981\u65bc {authkeys_url} \u65b0\u589e\u4e00\u7d44 API \u91d1\u9470\u3002\n\nTailnet \u70ba Tailscale \u7db2\u8def\u7684\u540d\u7a31\uff0c\u53ef\u4ee5\u65bc Tailscale \u7ba1\u7406\u9762\u677f\uff08Tailscale Logo \u65c1\uff09\u7684\u5de6\u4e0a\u65b9\u627e\u5230\u6b64\u8cc7\u8a0a\u3002" } } } diff --git a/homeassistant/components/tankerkoenig/binary_sensor.py b/homeassistant/components/tankerkoenig/binary_sensor.py index 4b58ea26703..9a2b048e0b8 100644 --- a/homeassistant/components/tankerkoenig/binary_sensor.py +++ b/homeassistant/components/tankerkoenig/binary_sensor.py @@ -74,5 +74,5 @@ class StationOpenBinarySensorEntity(CoordinatorEntity, BinarySensorEntity): @property def is_on(self) -> bool | None: """Return true if the station is open.""" - data = self.coordinator.data[self._station_id] - return data is not None and "status" in data + data: dict = self.coordinator.data[self._station_id] + return data is not None and data.get("status") == "open" diff --git a/homeassistant/components/tankerkoenig/config_flow.py b/homeassistant/components/tankerkoenig/config_flow.py index 3f3449c26e4..65c367d1ba4 100644 --- a/homeassistant/components/tankerkoenig/config_flow.py +++ b/homeassistant/components/tankerkoenig/config_flow.py @@ -15,13 +15,16 @@ from homeassistant.const import ( CONF_NAME, CONF_RADIUS, CONF_SHOW_ON_MAP, - CONF_UNIT_OF_MEASUREMENT, LENGTH_KILOMETERS, ) from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.selector import selector +from homeassistant.helpers.selector import ( + LocationSelector, + NumberSelector, + NumberSelectorConfig, +) from .const import CONF_FUEL_TYPES, CONF_STATIONS, DEFAULT_RADIUS, DOMAIN, FUEL_TYPES @@ -154,18 +157,16 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): "longitude": self.hass.config.longitude, }, ), - ): selector({"location": {}}), + ): LocationSelector(), vol.Required( CONF_RADIUS, default=user_input.get(CONF_RADIUS, DEFAULT_RADIUS) - ): selector( - { - "number": { - "min": 0.1, - "max": 25, - "step": 0.1, - CONF_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS, - } - } + ): NumberSelector( + NumberSelectorConfig( + min=0.1, + max=25, + step=0.1, + unit_of_measurement=LENGTH_KILOMETERS, + ), ), } ), diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index bbaeda44fd7..898a38c3c14 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity +from homeassistant.components.sensor import SensorEntity, SensorStateClass from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -65,7 +65,7 @@ async def async_setup_entry( class FuelPriceSensor(CoordinatorEntity, SensorEntity): """Contains prices for fuel in a given station.""" - _attr_state_class = STATE_CLASS_MEASUREMENT + _attr_state_class = SensorStateClass.MEASUREMENT _attr_icon = "mdi:gas-station" def __init__(self, fuel_type, station, coordinator, show_on_map): diff --git a/homeassistant/components/tankerkoenig/translations/bg.json b/homeassistant/components/tankerkoenig/translations/bg.json new file mode 100644 index 00000000000..700c4f3000b --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/bg.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447", + "fuel_types": "\u0412\u0438\u0434\u043e\u0432\u0435 \u0433\u043e\u0440\u0438\u0432\u043e", + "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", + "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 \u043d\u0430 \u0442\u044a\u0440\u0441\u0435\u043d\u0435" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043d\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/ca.json b/homeassistant/components/tankerkoenig/translations/ca.json new file mode 100644 index 00000000000..676bb1ccb55 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/ca.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "no_stations": "No s'ha pogut trobar cap estaci\u00f3 a l'abast." + }, + "step": { + "select_station": { + "data": { + "stations": "Estacions" + }, + "description": "S'han trobat {stations_count} estacions dins el radi", + "title": "Selecciona les estacions a afegir" + }, + "user": { + "data": { + "api_key": "Clau API", + "fuel_types": "Tipus de combustible", + "location": "Ubicaci\u00f3", + "name": "Nom de la regi\u00f3", + "radius": "Radi de cerca", + "stations": "Estacions de servei addicionals" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval d'actualitzaci\u00f3", + "show_on_map": "Mostra les estacions al mapa" + }, + "title": "Opcions de Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/de.json b/homeassistant/components/tankerkoenig/translations/de.json new file mode 100644 index 00000000000..3c2a5f1ec72 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/de.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "no_stations": "Konnte keine Station in Reichweite finden." + }, + "step": { + "select_station": { + "data": { + "stations": "Stationen" + }, + "description": "{stations_count} Stationen im Umkreis gefunden", + "title": "W\u00e4hle Stationen zum Hinzuf\u00fcgen aus" + }, + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "fuel_types": "Kraftstoffarten", + "location": "Standort", + "name": "Name der Region", + "radius": "Suchradius", + "stations": "Zus\u00e4tzliche Tankstellen" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Update-Intervall", + "show_on_map": "Stationen auf der Karte anzeigen" + }, + "title": "Tankerkoenig Optionen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/el.json b/homeassistant/components/tankerkoenig/translations/el.json new file mode 100644 index 00000000000..7f814b9760c --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/el.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "no_stations": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03b5\u03bd\u03c4\u03cc\u03c2 \u03b5\u03bc\u03b2\u03ad\u03bb\u03b5\u03b9\u03b1\u03c2." + }, + "step": { + "select_station": { + "data": { + "stations": "\u03a3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af" + }, + "description": "\u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd {stations_count} \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03c3\u03b5 \u03b1\u03ba\u03c4\u03af\u03bd\u03b1", + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7" + }, + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "fuel_types": "\u03a4\u03cd\u03c0\u03bf\u03b9 \u03ba\u03b1\u03c5\u03c3\u03af\u03bc\u03c9\u03bd", + "location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c7\u03ae\u03c2", + "radius": "\u0391\u03ba\u03c4\u03af\u03bd\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2", + "stations": "\u03a0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03b1 \u03c0\u03c1\u03b1\u03c4\u03ae\u03c1\u03b9\u03b1 \u03ba\u03b1\u03c5\u03c3\u03af\u03bc\u03c9\u03bd" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2", + "show_on_map": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c3\u03c4\u03b1\u03b8\u03bc\u03ce\u03bd \u03c3\u03c4\u03bf \u03c7\u03ac\u03c1\u03c4\u03b7" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/et.json b/homeassistant/components/tankerkoenig/translations/et.json new file mode 100644 index 00000000000..c15e4b78ddf --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/et.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Asukoht on juba m\u00e4\u00e4ratud" + }, + "error": { + "invalid_auth": "Tuvastamine nurjus", + "no_stations": "Piirkonnas ei leitud \u00fchtegi tanklat" + }, + "step": { + "select_station": { + "data": { + "stations": "Tanklad" + }, + "description": "piirkonnas on leitud {stations_count} tanklat", + "title": "Vali lisatavad tanklad" + }, + "user": { + "data": { + "api_key": "API v\u00f5ti", + "fuel_types": "K\u00fctuse liigid", + "location": "Asukoht", + "name": "Piirkonna nimi", + "radius": "Otsingu raadius", + "stations": "T\u00e4iendavad tanklad" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "V\u00e4rskendamise intervall", + "show_on_map": "N\u00e4ita jaamu kaardil" + }, + "title": "Tankerkoenig valikud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/fr.json b/homeassistant/components/tankerkoenig/translations/fr.json new file mode 100644 index 00000000000..1a52eee5d39 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/fr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "L'emplacement est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "invalid_auth": "Authentification non valide", + "no_stations": "Aucune station-service n'a \u00e9t\u00e9 trouv\u00e9e dans le rayon indiqu\u00e9." + }, + "step": { + "select_station": { + "data": { + "stations": "Stations-services" + }, + "description": "{stations_count}\u00a0stations-services trouv\u00e9es dans le rayon", + "title": "S\u00e9lectionnez les stations-services \u00e0 ajouter" + }, + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "fuel_types": "Types de carburant", + "location": "Emplacement", + "name": "Nom de la r\u00e9gion", + "radius": "Rayon de recherche", + "stations": "Stations-services suppl\u00e9mentaires" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalle de mise \u00e0 jour", + "show_on_map": "Afficher les stations-services sur la carte" + }, + "title": "Options Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/he.json b/homeassistant/components/tankerkoenig/translations/he.json new file mode 100644 index 00000000000..9ccb9aecfe6 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05de\u05d9\u05e7\u05d5\u05dd \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "error": { + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "location": "\u05de\u05d9\u05e7\u05d5\u05dd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/hu.json b/homeassistant/components/tankerkoenig/translations/hu.json new file mode 100644 index 00000000000..e2c31e9e354 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/hu.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "no_stations": "Nem tal\u00e1lhat\u00f3 \u00e1llom\u00e1s a hat\u00f3t\u00e1vols\u00e1gon bel\u00fcl." + }, + "step": { + "select_station": { + "data": { + "stations": "\u00c1llom\u00e1sok" + }, + "description": "{stations_count} \u00e1llom\u00e1s tal\u00e1lhat\u00f3 a hat\u00f3t\u00e1vols\u00e1gon bel\u00fcl", + "title": "Hozz\u00e1adand\u00f3 \u00e1llom\u00e1sok kiv\u00e1laszt\u00e1sa" + }, + "user": { + "data": { + "api_key": "API kulcs", + "fuel_types": "\u00dczemanyag t\u00edpusok", + "location": "Elhelyezked\u00e9s", + "name": "R\u00e9gi\u00f3 neve", + "radius": "Keres\u00e9s hat\u00f3t\u00e1vols\u00e1ga", + "stations": "Tov\u00e1bbi \u00fczemanyagt\u00f6lt\u0151 \u00e1llom\u00e1sok" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Friss\u00edt\u00e9si id\u0151k\u00f6z", + "show_on_map": "\u00c1llom\u00e1sok megjelen\u00edt\u00e9se a t\u00e9rk\u00e9pen" + }, + "title": "Tankerkoenig be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/id.json b/homeassistant/components/tankerkoenig/translations/id.json new file mode 100644 index 00000000000..cddeb02b17f --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "no_stations": "Tidak dapat menemukan SPBU dalam jangkauan." + }, + "step": { + "select_station": { + "data": { + "stations": "SPBU" + }, + "description": "ditemukan {stations_count} SPBU dalam radius", + "title": "Pilih SPBU untuk ditambahkan" + }, + "user": { + "data": { + "api_key": "Kunci API", + "fuel_types": "Jenis bahan bakar", + "location": "Lokasi", + "name": "Nama wilayah", + "radius": "Radius pencarian", + "stations": "SPBU tambahan" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pembaruan", + "show_on_map": "Tampilkan SPBU di peta" + }, + "title": "Opsi Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/it.json b/homeassistant/components/tankerkoenig/translations/it.json new file mode 100644 index 00000000000..e24c353d4f1 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/it.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "La posizione \u00e8 gi\u00e0 configurata" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "no_stations": "Impossibile trovare nessuna stazione nel raggio d'azione." + }, + "step": { + "select_station": { + "data": { + "stations": "Stazioni" + }, + "description": "trovato {stations_count} stazioni nel raggio", + "title": "Seleziona le stazioni da aggiungere" + }, + "user": { + "data": { + "api_key": "Chiave API", + "fuel_types": "Tipi di carburante", + "location": "Posizione", + "name": "Nome della regione", + "radius": "Raggio di ricerca", + "stations": "Stazioni di servizio aggiuntive" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervallo di aggiornamento", + "show_on_map": "Mostra stazioni sulla mappa" + }, + "title": "Opzioni Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/ja.json b/homeassistant/components/tankerkoenig/translations/ja.json new file mode 100644 index 00000000000..687e05322d5 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/ja.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" + }, + "error": { + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "no_stations": "\u7bc4\u56f2\u5185\u306b\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002" + }, + "step": { + "select_station": { + "data": { + "stations": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3" + }, + "description": "\u534a\u5f84\u5185\u306b {stations_count} \u500b\u306e\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f", + "title": "\u8ffd\u52a0\u3059\u308b\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u3092\u9078\u629e\u3057\u307e\u3059" + }, + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "fuel_types": "\u71c3\u6599\u306e\u7a2e\u985e", + "location": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3", + "name": "\u5730\u57df\u540d", + "radius": "\u691c\u7d22\u534a\u5f84", + "stations": "\u8ffd\u52a0\u306e\u71c3\u6599\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u66f4\u65b0\u9593\u9694", + "show_on_map": "\u5730\u56f3\u4e0a\u306b\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u3092\u8868\u793a\u3059\u308b" + }, + "title": "Tankerkoenig\u30aa\u30d7\u30b7\u30e7\u30f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/nl.json b/homeassistant/components/tankerkoenig/translations/nl.json new file mode 100644 index 00000000000..96de058ad74 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/nl.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Locatie is al geconfigureerd." + }, + "error": { + "invalid_auth": "Ongeldige authenticatie", + "no_stations": "Kon geen station in bereik vinden." + }, + "step": { + "select_station": { + "data": { + "stations": "Stations" + }, + "description": "{stations_count} gevonden in radius", + "title": "Selecteer stations om toe te voegen" + }, + "user": { + "data": { + "api_key": "API-sleutel", + "fuel_types": "Brandstofsoorten", + "location": "Locatie", + "name": "Regionaam", + "radius": "Zoekradius", + "stations": "Extra tankstations" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Update Interval", + "show_on_map": "Toon stations op kaart" + }, + "title": "Tankerkoenig opties" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/no.json b/homeassistant/components/tankerkoenig/translations/no.json new file mode 100644 index 00000000000..9d0b6ddab52 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/no.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Plasseringen er allerede konfigurert" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning", + "no_stations": "Kunne ikke finne noen stasjon innen rekkevidde." + }, + "step": { + "select_station": { + "data": { + "stations": "Stasjoner" + }, + "description": "fant {stations_count} stasjoner i radius", + "title": "Velg stasjoner du vil legge til" + }, + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "fuel_types": "Drivstofftyper", + "location": "Plassering", + "name": "Navn p\u00e5 omr\u00e5de", + "radius": "Radius for s\u00f8k", + "stations": "Flere bensinstasjoner" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Oppdateringsintervall", + "show_on_map": "Vis stasjoner p\u00e5 kart" + }, + "title": "Tankerkoenig alternativer" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/pl.json b/homeassistant/components/tankerkoenig/translations/pl.json new file mode 100644 index 00000000000..e13ae2c4783 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/pl.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Lokalizacja jest ju\u017c skonfigurowana" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "no_stations": "Nie mo\u017cna znale\u017a\u0107 \u017cadnej stacji w zasi\u0119gu." + }, + "step": { + "select_station": { + "data": { + "stations": "Stacje" + }, + "description": "liczba znalezionych stacji w promieniu: {stations_count}", + "title": "Wybierz stacje do dodania" + }, + "user": { + "data": { + "api_key": "Klucz API", + "fuel_types": "Rodzaje paliw", + "location": "Lokalizacja", + "name": "Nazwa regionu", + "radius": "Promie\u0144 wyszukiwania", + "stations": "Dodatkowe stacje paliw" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji", + "show_on_map": "Poka\u017c stacje na mapie" + }, + "title": "Opcje Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/pt-BR.json b/homeassistant/components/tankerkoenig/translations/pt-BR.json new file mode 100644 index 00000000000..b24e0b1dca8 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/pt-BR.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_stations": "N\u00e3o foi poss\u00edvel encontrar nenhum posto ao alcance." + }, + "step": { + "select_station": { + "data": { + "stations": "Postos de combustiveis" + }, + "description": "encontrou {stations_count} postos no raio", + "title": "Selecione postos para adicionar" + }, + "user": { + "data": { + "api_key": "Chave da API", + "fuel_types": "Tipos de combust\u00edvel", + "location": "Localiza\u00e7\u00e3o", + "name": "Nome da regi\u00e3o", + "radius": "Raio de pesquisa", + "stations": "Postos de combust\u00edvel adicionais" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de atualiza\u00e7\u00e3o", + "show_on_map": "Mostrar postos no mapa" + }, + "title": "Op\u00e7\u00f5es de Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/ru.json b/homeassistant/components/tankerkoenig/translations/ru.json new file mode 100644 index 00000000000..bf61fddc0c5 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/ru.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "error": { + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "no_stations": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u043d\u0438 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u0432 \u0440\u0430\u0434\u0438\u0443\u0441\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f." + }, + "step": { + "select_station": { + "data": { + "stations": "\u0421\u0442\u0430\u043d\u0446\u0438\u0438" + }, + "description": "\u041d\u0430\u0439\u0434\u0435\u043d\u043e {stations_count} \u0441\u0442\u0430\u043d\u0446\u0438\u0439 \u0432 \u0440\u0430\u0434\u0438\u0443\u0441\u0435.", + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0430\u043d\u0446\u0438\u0438" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "fuel_types": "\u0412\u0438\u0434\u044b \u0442\u043e\u043f\u043b\u0438\u0432\u0430", + "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u0433\u0438\u043e\u043d\u0430", + "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 \u043f\u043e\u0438\u0441\u043a\u0430", + "stations": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u044b\u0435 \u0441\u0442\u0430\u043d\u0446\u0438\u0438" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f", + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0438\u0438 \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Tankerkoenig" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/tr.json b/homeassistant/components/tankerkoenig/translations/tr.json new file mode 100644 index 00000000000..2d88d2fa670 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "no_stations": "Menzilde herhangi bir istasyon bulunamad\u0131." + }, + "step": { + "select_station": { + "data": { + "stations": "\u0130stasyonlar" + }, + "description": "yar\u0131\u00e7ap i\u00e7inde {stations_count} istasyon bulundu", + "title": "Eklenecek istasyonlar\u0131 se\u00e7in" + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "fuel_types": "Yak\u0131t t\u00fcrleri", + "location": "Konum", + "name": "B\u00f6lge ad\u0131", + "radius": "Yar\u0131\u00e7ap\u0131 ara\u015ft\u0131r", + "stations": "Ek yak\u0131t istasyonlar\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "G\u00fcncelle\u015ftirme aral\u0131\u011f\u0131", + "show_on_map": "\u0130stasyonlar\u0131 haritada g\u00f6ster" + }, + "title": "Tankerkoenig se\u00e7enekleri" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tankerkoenig/translations/zh-Hant.json b/homeassistant/components/tankerkoenig/translations/zh-Hant.json new file mode 100644 index 00000000000..059d07ccdc6 --- /dev/null +++ b/homeassistant/components/tankerkoenig/translations/zh-Hant.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\u5ea7\u6a19\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "no_stations": "\u7bc4\u570d\u5167\u627e\u4e0d\u5230\u4efb\u4f55\u52a0\u6cb9\u7ad9\u3002" + }, + "step": { + "select_station": { + "data": { + "stations": "\u52a0\u6cb9\u7ad9" + }, + "description": "\u65bc\u534a\u5f91\u5167\u627e\u5230 {stations_count} \u5ea7\u52a0\u6cb9\u7ad9", + "title": "\u9078\u64c7\u65b0\u589e\u52a0\u6cb9\u7ad9" + }, + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "fuel_types": "\u71c3\u6599\u985e\u5225", + "location": "\u5ea7\u6a19", + "name": "\u5340\u57df\u540d\u7a31", + "radius": "\u641c\u5c0b\u534a\u5f91", + "stations": "\u5176\u4ed6\u52a0\u6cb9\u7ad9" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u66f4\u65b0\u983b\u7387", + "show_on_map": "\u65bc\u5730\u5716\u986f\u793a\u52a0\u6cb9\u7ad9" + }, + "title": "Tankerkoenig \u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/cover.py b/homeassistant/components/tasmota/cover.py index 0b67e469929..172e460c29b 100644 --- a/homeassistant/components/tasmota/cover.py +++ b/homeassistant/components/tasmota/cover.py @@ -7,8 +7,12 @@ from hatasmota import const as tasmota_const, shutter as tasmota_shutter from hatasmota.entity import TasmotaEntity as HATasmotaEntity from hatasmota.models import DiscoveryHashType -from homeassistant.components import cover -from homeassistant.components.cover import CoverEntity +from homeassistant.components.cover import ( + ATTR_POSITION, + DOMAIN as COVER_DOMAIN, + CoverEntity, + CoverEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -36,10 +40,10 @@ async def async_setup_entry( ) hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(cover.DOMAIN) + DATA_REMOVE_DISCOVER_COMPONENT.format(COVER_DOMAIN) ] = async_dispatcher_connect( hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(cover.DOMAIN), + TASMOTA_DISCOVERY_ENTITY_NEW.format(COVER_DOMAIN), async_discover, ) @@ -51,6 +55,12 @@ class TasmotaCover( ): """Representation of a Tasmota cover.""" + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + ) _tasmota_entity: tasmota_shutter.TasmotaShutter def __init__(self, **kwds: Any) -> None: @@ -82,16 +92,6 @@ class TasmotaCover( """ return self._position - @property - def supported_features(self) -> int: - """Flag supported features.""" - return ( - cover.SUPPORT_OPEN - | cover.SUPPORT_CLOSE - | cover.SUPPORT_STOP - | cover.SUPPORT_SET_POSITION - ) - @property def is_opening(self) -> bool: """Return if the cover is opening or not.""" @@ -119,7 +119,7 @@ class TasmotaCover( async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" - position = kwargs[cover.ATTR_POSITION] + position = kwargs[ATTR_POSITION] await self._tasmota_entity.set_position(position) async def async_stop_cover(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/tasmota/fan.py b/homeassistant/components/tasmota/fan.py index a0002ff85f8..8a1deee7d60 100644 --- a/homeassistant/components/tasmota/fan.py +++ b/homeassistant/components/tasmota/fan.py @@ -7,8 +7,11 @@ from hatasmota import const as tasmota_const, fan as tasmota_fan from hatasmota.entity import TasmotaEntity as HATasmotaEntity from hatasmota.models import DiscoveryHashType -from homeassistant.components import fan -from homeassistant.components.fan import FanEntity +from homeassistant.components.fan import ( + DOMAIN as FAN_DOMAIN, + FanEntity, + FanEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -46,10 +49,10 @@ async def async_setup_entry( ) hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(fan.DOMAIN) + DATA_REMOVE_DISCOVER_COMPONENT.format(FAN_DOMAIN) ] = async_dispatcher_connect( hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(fan.DOMAIN), + TASMOTA_DISCOVERY_ENTITY_NEW.format(FAN_DOMAIN), async_discover, ) @@ -61,6 +64,7 @@ class TasmotaFan( ): """Representation of a Tasmota fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED _tasmota_entity: tasmota_fan.TasmotaFan def __init__(self, **kwds: Any) -> None: @@ -96,11 +100,6 @@ class TasmotaFan( return 0 return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, self._state) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return fan.SUPPORT_SET_SPEED - async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan.""" if percentage == 0: diff --git a/homeassistant/components/tasmota/light.py b/homeassistant/components/tasmota/light.py index e739967b48b..970d006e79d 100644 --- a/homeassistant/components/tasmota/light.py +++ b/homeassistant/components/tasmota/light.py @@ -22,14 +22,9 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - COLOR_MODE_WHITE, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, brightness_supported, ) from homeassistant.config_entries import ConfigEntry @@ -99,7 +94,6 @@ class TasmotaLight( def __init__(self, **kwds: Any) -> None: """Initialize Tasmota light.""" self._supported_color_modes: set[str] | None = None - self._supported_features = 0 self._brightness: int | None = None self._color_mode: str | None = None @@ -132,31 +126,31 @@ class TasmotaLight( if light_type in [LIGHT_TYPE_RGB, LIGHT_TYPE_RGBW, LIGHT_TYPE_RGBCW]: # Mark HS support for RGBW light because we don't have direct control over the # white channel, so the base component's RGB->RGBW translation does not work - self._supported_color_modes.add(COLOR_MODE_HS) - self._color_mode = COLOR_MODE_HS + self._supported_color_modes.add(ColorMode.HS) + self._color_mode = ColorMode.HS if light_type == LIGHT_TYPE_RGBW: - self._supported_color_modes.add(COLOR_MODE_WHITE) + self._supported_color_modes.add(ColorMode.WHITE) if light_type in [LIGHT_TYPE_COLDWARM, LIGHT_TYPE_RGBCW]: - self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP) - self._color_mode = COLOR_MODE_COLOR_TEMP + self._supported_color_modes.add(ColorMode.COLOR_TEMP) + self._color_mode = ColorMode.COLOR_TEMP if light_type != LIGHT_TYPE_NONE and not self._supported_color_modes: - self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS) - self._color_mode = COLOR_MODE_BRIGHTNESS + self._supported_color_modes.add(ColorMode.BRIGHTNESS) + self._color_mode = ColorMode.BRIGHTNESS if not self._supported_color_modes: - self._supported_color_modes.add(COLOR_MODE_ONOFF) - self._color_mode = COLOR_MODE_ONOFF + self._supported_color_modes.add(ColorMode.ONOFF) + self._color_mode = ColorMode.ONOFF if light_type in [LIGHT_TYPE_RGB, LIGHT_TYPE_RGBW, LIGHT_TYPE_RGBCW]: - supported_features |= SUPPORT_EFFECT + supported_features |= LightEntityFeature.EFFECT if self._tasmota_entity.supports_transition: - supported_features |= SUPPORT_TRANSITION + supported_features |= LightEntityFeature.TRANSITION - self._supported_features = supported_features + self._attr_supported_features = supported_features @callback def state_updated(self, state: bool, **kwargs: Any) -> None: @@ -180,15 +174,15 @@ class TasmotaLight( if self._tasmota_entity.light_type == LIGHT_TYPE_RGBW: # Tasmota does not support RGBW mode, set mode to white or hs if self._white_value == 0: - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS else: - self._color_mode = COLOR_MODE_WHITE + self._color_mode = ColorMode.WHITE elif self._tasmota_entity.light_type == LIGHT_TYPE_RGBCW: # Tasmota does not support RGBWW mode, set mode to ct or hs if self._white_value == 0: - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS else: - self._color_mode = COLOR_MODE_COLOR_TEMP + self._color_mode = ColorMode.COLOR_TEMP self.async_write_ha_state() @@ -245,22 +239,17 @@ class TasmotaLight( """Flag supported color modes.""" return self._supported_color_modes - @property - def supported_features(self) -> int: - """Flag supported features.""" - return self._supported_features - async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" supported_color_modes = self._supported_color_modes or set() attributes: dict[str, Any] = {} - if ATTR_HS_COLOR in kwargs and COLOR_MODE_HS in supported_color_modes: + if ATTR_HS_COLOR in kwargs and ColorMode.HS in supported_color_modes: hs_color = kwargs[ATTR_HS_COLOR] attributes["color_hs"] = [hs_color[0], hs_color[1]] - if ATTR_WHITE in kwargs and COLOR_MODE_WHITE in supported_color_modes: + if ATTR_WHITE in kwargs and ColorMode.WHITE in supported_color_modes: attributes["white_value"] = scale_brightness(kwargs[ATTR_WHITE]) if ATTR_TRANSITION in kwargs: @@ -269,7 +258,7 @@ class TasmotaLight( if ATTR_BRIGHTNESS in kwargs and brightness_supported(supported_color_modes): attributes["brightness"] = scale_brightness(kwargs[ATTR_BRIGHTNESS]) - if ATTR_COLOR_TEMP in kwargs and COLOR_MODE_COLOR_TEMP in supported_color_modes: + if ATTR_COLOR_TEMP in kwargs and ColorMode.COLOR_TEMP in supported_color_modes: attributes["color_temp"] = int(kwargs[ATTR_COLOR_TEMP]) if ATTR_EFFECT in kwargs: diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 2f3a1b66fea..6a743683d94 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.4.0"], + "requirements": ["hatasmota==0.4.1"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"], diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index feb15cd5639..34526904f17 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -54,11 +54,24 @@ ICON = "icon" # A Tasmota sensor type may be mapped to either a device class or an icon, not both SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { + hc.SENSOR_ACTIVE_ENERGYEXPORT: { + DEVICE_CLASS: SensorDeviceClass.ENERGY, + STATE_CLASS: SensorStateClass.TOTAL, + }, + hc.SENSOR_ACTIVE_ENERGYIMPORT: { + DEVICE_CLASS: SensorDeviceClass.ENERGY, + STATE_CLASS: SensorStateClass.TOTAL, + }, + hc.SENSOR_ACTIVE_POWERUSAGE: { + DEVICE_CLASS: SensorDeviceClass.POWER, + STATE_CLASS: SensorStateClass.MEASUREMENT, + }, hc.SENSOR_AMBIENT: { DEVICE_CLASS: SensorDeviceClass.ILLUMINANCE, STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_APPARENT_POWERUSAGE: { + ICON: "mdi:flash", STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_BATTERY: { @@ -80,6 +93,11 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { ICON: "mdi:alpha-a-circle-outline", STATE_CLASS: SensorStateClass.MEASUREMENT, }, + hc.SENSOR_CURRENTNEUTRAL: { + ICON: "mdi:alpha-a-circle-outline", + DEVICE_CLASS: SensorDeviceClass.CURRENT, + STATE_CLASS: SensorStateClass.MEASUREMENT, + }, hc.SENSOR_DEWPOINT: { DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, ICON: "mdi:weather-rainy", @@ -141,7 +159,10 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_PROXIMITY: {ICON: "mdi:ruler"}, + hc.SENSOR_REACTIVE_ENERGYEXPORT: {STATE_CLASS: SensorStateClass.TOTAL}, + hc.SENSOR_REACTIVE_ENERGYIMPORT: {STATE_CLASS: SensorStateClass.TOTAL}, hc.SENSOR_REACTIVE_POWERUSAGE: { + ICON: "mdi:flash", STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_STATUS_LAST_RESTART_TIME: {DEVICE_CLASS: SensorDeviceClass.TIMESTAMP}, @@ -162,7 +183,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { hc.SENSOR_TODAY: {DEVICE_CLASS: SensorDeviceClass.ENERGY}, hc.SENSOR_TOTAL: { DEVICE_CLASS: SensorDeviceClass.ENERGY, - STATE_CLASS: SensorStateClass.TOTAL_INCREASING, + STATE_CLASS: SensorStateClass.TOTAL, }, hc.SENSOR_TOTAL_START_TIME: {ICON: "mdi:progress-clock"}, hc.SENSOR_TVOC: {ICON: "mdi:air-filter"}, diff --git a/homeassistant/components/tautulli/__init__.py b/homeassistant/components/tautulli/__init__.py index f8ce5ca08b5..fe6eeb9e303 100644 --- a/homeassistant/components/tautulli/__init__.py +++ b/homeassistant/components/tautulli/__init__.py @@ -1 +1,63 @@ -"""The tautulli component.""" +"""The Tautulli integration.""" +from __future__ import annotations + +from pytautulli import PyTautulli, PyTautulliHostConfiguration + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DEFAULT_NAME, DOMAIN +from .coordinator import TautulliDataUpdateCoordinator + +PLATFORMS = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Tautulli from a config entry.""" + host_configuration = PyTautulliHostConfiguration( + api_token=entry.data[CONF_API_KEY], + url=entry.data[CONF_URL], + verify_ssl=entry.data[CONF_VERIFY_SSL], + ) + api_client = PyTautulli( + host_configuration=host_configuration, + session=async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]), + ) + coordinator = TautulliDataUpdateCoordinator(hass, host_configuration, api_client) + await coordinator.async_config_entry_first_refresh() + hass.data[DOMAIN] = coordinator + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data.pop(DOMAIN) + return unload_ok + + +class TautulliEntity(CoordinatorEntity[TautulliDataUpdateCoordinator]): + """Defines a base Tautulli entity.""" + + def __init__( + self, + coordinator: TautulliDataUpdateCoordinator, + description: EntityDescription, + ) -> None: + """Initialize the Tautulli entity.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{description.key}" + self._attr_device_info = DeviceInfo( + configuration_url=coordinator.host_configuration.base_url, + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, + manufacturer=DEFAULT_NAME, + ) diff --git a/homeassistant/components/tautulli/config_flow.py b/homeassistant/components/tautulli/config_flow.py new file mode 100644 index 00000000000..ea470e2e1d0 --- /dev/null +++ b/homeassistant/components/tautulli/config_flow.py @@ -0,0 +1,142 @@ +"""Config flow for Tautulli.""" +from __future__ import annotations + +from typing import Any + +from pytautulli import ( + PyTautulli, + PyTautulliException, + PyTautulliHostConfiguration, + exceptions, +) +import voluptuous as vol + +from homeassistant.components.sensor import _LOGGER +from homeassistant.config_entries import ConfigFlow +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PATH, + CONF_PORT, + CONF_SSL, + CONF_URL, + CONF_VERIFY_SSL, +) +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import ( + DEFAULT_NAME, + DEFAULT_PATH, + DEFAULT_PORT, + DEFAULT_SSL, + DEFAULT_VERIFY_SSL, + DOMAIN, +) + + +class TautulliConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Tautulli.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initiated by the user.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + errors = {} + if user_input is not None: + if (error := await self.validate_input(user_input)) is None: + return self.async_create_entry( + title=DEFAULT_NAME, + data=user_input, + ) + errors["base"] = error + + user_input = user_input or {} + data_schema = { + vol.Required(CONF_API_KEY, default=user_input.get(CONF_API_KEY, "")): str, + vol.Required(CONF_URL, default=user_input.get(CONF_URL, "")): str, + vol.Optional( + CONF_VERIFY_SSL, default=user_input.get(CONF_VERIFY_SSL, True) + ): bool, + } + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema(data_schema), + errors=errors or {}, + ) + + async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult: + """Handle a reauthorization flow request.""" + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, str] | None = None + ) -> FlowResult: + """Confirm reauth dialog.""" + errors = {} + if user_input is not None and ( + entry := self.hass.config_entries.async_get_entry(self.context["entry_id"]) + ): + _input = {**entry.data, CONF_API_KEY: user_input[CONF_API_KEY]} + if (error := await self.validate_input(_input)) is None: + self.hass.config_entries.async_update_entry(entry, data=_input) + await self.hass.config_entries.async_reload(entry.entry_id) + return self.async_abort(reason="reauth_successful") + errors["base"] = error + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}), + errors=errors, + ) + + async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + """Import a config entry from configuration.yaml.""" + _LOGGER.warning( + "Configuration of the Tautulli platform in YAML is deprecated and will be " + "removed in Home Assistant 2022.6; Your existing configuration for host %s" + "has been imported into the UI automatically and can be safely removed " + "from your configuration.yaml file", + config[CONF_HOST], + ) + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + host_configuration = PyTautulliHostConfiguration( + config[CONF_API_KEY], + ipaddress=config[CONF_HOST], + port=config.get(CONF_PORT, DEFAULT_PORT), + ssl=config.get(CONF_SSL, DEFAULT_SSL), + verify_ssl=config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL), + base_api_path=config.get(CONF_PATH, DEFAULT_PATH), + ) + return await self.async_step_user( + { + CONF_API_KEY: host_configuration.api_token, + CONF_URL: host_configuration.base_url, + CONF_VERIFY_SSL: host_configuration.verify_ssl, + } + ) + + async def validate_input(self, user_input: dict[str, Any]) -> str | None: + """Try connecting to Tautulli.""" + try: + api_client = PyTautulli( + api_token=user_input[CONF_API_KEY], + url=user_input[CONF_URL], + session=async_get_clientsession( + self.hass, user_input.get(CONF_VERIFY_SSL, True) + ), + verify_ssl=user_input.get(CONF_VERIFY_SSL, True), + ) + await api_client.async_get_server_info() + except exceptions.PyTautulliConnectionException: + return "cannot_connect" + except exceptions.PyTautulliAuthenticationException: + return "invalid_auth" + except PyTautulliException: + return "unknown" + return None diff --git a/homeassistant/components/tautulli/const.py b/homeassistant/components/tautulli/const.py index a7427e401ba..5c0a1b56cda 100644 --- a/homeassistant/components/tautulli/const.py +++ b/homeassistant/components/tautulli/const.py @@ -1,5 +1,11 @@ """Constants for the Tautulli integration.""" from logging import Logger, getLogger +CONF_MONITORED_USERS = "monitored_users" +DEFAULT_NAME = "Tautulli" +DEFAULT_PATH = "" +DEFAULT_PORT = "8181" +DEFAULT_SSL = False +DEFAULT_VERIFY_SSL = True DOMAIN = "tautulli" LOGGER: Logger = getLogger(__package__) diff --git a/homeassistant/components/tautulli/coordinator.py b/homeassistant/components/tautulli/coordinator.py index 6ca2ed0d7d6..a71a7580740 100644 --- a/homeassistant/components/tautulli/coordinator.py +++ b/homeassistant/components/tautulli/coordinator.py @@ -9,10 +9,16 @@ from pytautulli import ( PyTautulliApiActivity, PyTautulliApiHomeStats, PyTautulliApiUser, - PyTautulliException, ) +from pytautulli.exceptions import ( + PyTautulliAuthenticationException, + PyTautulliConnectionException, +) +from pytautulli.models.host_configuration import PyTautulliHostConfiguration +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, LOGGER @@ -21,9 +27,12 @@ from .const import DOMAIN, LOGGER class TautulliDataUpdateCoordinator(DataUpdateCoordinator): """Data update coordinator for the Tautulli integration.""" + config_entry: ConfigEntry + def __init__( self, hass: HomeAssistant, + host_configuration: PyTautulliHostConfiguration, api_client: PyTautulli, ) -> None: """Initialize the coordinator.""" @@ -33,6 +42,7 @@ class TautulliDataUpdateCoordinator(DataUpdateCoordinator): name=DOMAIN, update_interval=timedelta(seconds=10), ) + self.host_configuration = host_configuration self.api_client = api_client self.activity: PyTautulliApiActivity | None = None self.home_stats: list[PyTautulliApiHomeStats] | None = None @@ -48,5 +58,7 @@ class TautulliDataUpdateCoordinator(DataUpdateCoordinator): self.api_client.async_get_users(), ] ) - except PyTautulliException as exception: - raise UpdateFailed(exception) from exception + except PyTautulliConnectionException as ex: + raise UpdateFailed(ex) from ex + except PyTautulliAuthenticationException as ex: + raise ConfigEntryAuthFailed(ex) from ex diff --git a/homeassistant/components/tautulli/manifest.json b/homeassistant/components/tautulli/manifest.json index 06c2d4e0c6b..bbdaa4c8ebb 100644 --- a/homeassistant/components/tautulli/manifest.json +++ b/homeassistant/components/tautulli/manifest.json @@ -3,7 +3,8 @@ "name": "Tautulli", "documentation": "https://www.home-assistant.io/integrations/tautulli", "requirements": ["pytautulli==21.11.0"], - "codeowners": ["@ludeeus"], + "config_flow": true, + "codeowners": ["@ludeeus", "@tkdrob"], "iot_class": "local_polling", "loggers": ["pytautulli"] } diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index f28e334e1c0..b1af6e3ce47 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -3,10 +3,14 @@ from __future__ import annotations from typing import Any -from pytautulli import PyTautulli import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -18,22 +22,23 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity +from . import TautulliEntity +from .const import ( + CONF_MONITORED_USERS, + DEFAULT_NAME, + DEFAULT_PATH, + DEFAULT_PORT, + DEFAULT_SSL, + DEFAULT_VERIFY_SSL, + DOMAIN, +) from .coordinator import TautulliDataUpdateCoordinator -CONF_MONITORED_USERS = "monitored_users" - -DEFAULT_NAME = "Tautulli" -DEFAULT_PORT = "8181" -DEFAULT_PATH = "" -DEFAULT_SSL = False -DEFAULT_VERIFY_SSL = True - +# Deprecated in Home Assistant 2022.4 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_API_KEY): cv.string, @@ -48,6 +53,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) +SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + icon="mdi:plex", + key="watching_count", + name="Tautulli", + native_unit_of_measurement="Watching", + ), +) + async def async_setup_platform( hass: HomeAssistant, @@ -56,65 +70,30 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Create the Tautulli sensor.""" - - name = config[CONF_NAME] - host = config[CONF_HOST] - port = config[CONF_PORT] - path = config[CONF_PATH] - api_key = config[CONF_API_KEY] - monitored_conditions = config.get(CONF_MONITORED_CONDITIONS, []) - users = config.get(CONF_MONITORED_USERS, []) - use_ssl = config[CONF_SSL] - verify_ssl = config[CONF_VERIFY_SSL] - - session = async_get_clientsession(hass=hass, verify_ssl=verify_ssl) - - api_client = PyTautulli( - api_token=api_key, - hostname=host, - session=session, - verify_ssl=verify_ssl, - port=port, - ssl=use_ssl, - base_api_path=path, + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) ) - coordinator = TautulliDataUpdateCoordinator(hass=hass, api_client=api_client) +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Tautulli sensor.""" + coordinator: TautulliDataUpdateCoordinator = hass.data[DOMAIN] async_add_entities( - new_entities=[ - TautulliSensor( - coordinator=coordinator, - name=name, - monitored_conditions=monitored_conditions, - usernames=users, - ) - ], - update_before_add=True, + TautulliSensor( + coordinator, + description, + ) + for description in SENSOR_TYPES ) -class TautulliSensor(CoordinatorEntity[TautulliDataUpdateCoordinator], SensorEntity): +class TautulliSensor(TautulliEntity, SensorEntity): """Representation of a Tautulli sensor.""" - def __init__( - self, - coordinator: TautulliDataUpdateCoordinator, - name: str, - monitored_conditions: list[str], - usernames: list[str], - ) -> None: - """Initialize the Tautulli sensor.""" - super().__init__(coordinator) - self.monitored_conditions = monitored_conditions - self.usernames = usernames - self._name = name - - @property - def name(self) -> str: - """Return the name of the sensor.""" - return self._name - @property def native_value(self) -> StateType: """Return the state of the sensor.""" @@ -122,16 +101,6 @@ class TautulliSensor(CoordinatorEntity[TautulliDataUpdateCoordinator], SensorEnt return 0 return self.coordinator.activity.stream_count or 0 - @property - def icon(self) -> str: - """Return the icon of the sensor.""" - return "mdi:plex" - - @property - def native_unit_of_measurement(self) -> str: - """Return the unit this state is expressed in.""" - return "Watching" - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return attributes for the sensor.""" @@ -161,20 +130,14 @@ class TautulliSensor(CoordinatorEntity[TautulliDataUpdateCoordinator], SensorEnt _attributes["Top User"] = stat.rows[0].user if stat.rows else None for user in self.coordinator.users: - if ( - self.usernames - and user.username not in self.usernames - or user.username == "Local" - ): + if user.username == "Local": continue _attributes.setdefault(user.username, {})["Activity"] = None for session in self.coordinator.activity.sessions: - if not _attributes.get(session.username): + if not _attributes.get(session.username) or "null" in session.state: continue _attributes[session.username]["Activity"] = session.state - for key in self.monitored_conditions: - _attributes[session.username][key] = getattr(session, key) return _attributes diff --git a/homeassistant/components/tautulli/strings.json b/homeassistant/components/tautulli/strings.json new file mode 100644 index 00000000000..9b561ea6f5b --- /dev/null +++ b/homeassistant/components/tautulli/strings.json @@ -0,0 +1,30 @@ +{ + "config": { + "step": { + "user": { + "description": "To find your API key, open the Tautulli webpage and navigate to Settings and then to Web interface. The API key will be at the bottom of that page.\n\nExample of the URL: ```http://192.168.0.10:8181``` with 8181 being the default port.", + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "url": "[%key:common::config_flow::data::url%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + } + }, + "reauth_confirm": { + "title": "Re-authenticate Tautulli", + "description": "To find your API key, open the Tautulli webpage and navigate to Settings and then to Web interface. The API key will be at the bottom of that page.", + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + } + } +} diff --git a/homeassistant/components/tautulli/translations/de.json b/homeassistant/components/tautulli/translations/de.json new file mode 100644 index 00000000000..bb1e2bc8c95 --- /dev/null +++ b/homeassistant/components/tautulli/translations/de.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-Schl\u00fcssel" + }, + "description": "Um deinen API-Schl\u00fcssel zu finden, \u00f6ffne die Tautulli-Webseite und navigiere zu Einstellungen und dann zu Webinterface. Der API-Schl\u00fcssel befindet sich unten auf dieser Seite.", + "title": "Tautulli erneut authentifizieren" + }, + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "url": "URL", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + }, + "description": "Um deinen API-Schl\u00fcssel zu finden, \u00f6ffne die Tautulli-Webseite und navigiere zu Einstellungen und dann zu Webinterface. Der API-Schl\u00fcssel befindet sich unten auf dieser Seite.\n\nBeispiel f\u00fcr die URL: ```http://192.168.0.10:8181`` mit 8181 als Standard-Port." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/el.json b/homeassistant/components/tautulli/translations/el.json new file mode 100644 index 00000000000..a83662fb6e6 --- /dev/null +++ b/homeassistant/components/tautulli/translations/el.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2", + "single_instance_allowed": "\u0388\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af. \u039c\u03cc\u03bd\u03bf \u03bc\u03af\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae." + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03b1\u03bd\u03bf\u03af\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b9\u03c3\u03c4\u03bf\u03c3\u03b5\u03bb\u03af\u03b4\u03b1 Tautulli \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u0399\u03c3\u03c4\u03bf\u03cd. \u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b8\u03b1 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03ba\u03ac\u03c4\u03c9 \u03bc\u03ad\u03c1\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1\u03c2.", + "title": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 Tautulli" + }, + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc SSL" + }, + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03b1\u03bd\u03bf\u03af\u03be\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b9\u03c3\u03c4\u03bf\u03c3\u03b5\u03bb\u03af\u03b4\u03b1 Tautulli \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03c0\u03b1\u03c6\u03ae \u0399\u03c3\u03c4\u03bf\u03cd. \u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b8\u03b1 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf \u03ba\u03ac\u03c4\u03c9 \u03bc\u03ad\u03c1\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1\u03c2. \n\n \u03a0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7\u03c2 URL: ```http://192.168.0.10:8181``` \u03bc\u03b5 \u03c4\u03bf 8181 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b7 \u03b8\u03cd\u03c1\u03b1." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/en.json b/homeassistant/components/tautulli/translations/en.json new file mode 100644 index 00000000000..c24497cfbd9 --- /dev/null +++ b/homeassistant/components/tautulli/translations/en.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Re-authentication was successful", + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API Key" + }, + "description": "To find your API key, open the Tautulli webpage and navigate to Settings and then to Web interface. The API key will be at the bottom of that page.", + "title": "Re-authenticate Tautulli" + }, + "user": { + "data": { + "api_key": "API Key", + "url": "URL", + "verify_ssl": "Verify SSL certificate" + }, + "description": "To find your API key, open the Tautulli webpage and navigate to Settings and then to Web interface. The API key will be at the bottom of that page.\n\nExample of the URL: ```http://192.168.0.10:8181``` with 8181 being the default port." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/et.json b/homeassistant/components/tautulli/translations/et.json new file mode 100644 index 00000000000..bc690db7a28 --- /dev/null +++ b/homeassistant/components/tautulli/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Taastuvastamine \u00f5nnestus", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API v\u00f5ti" + }, + "description": "API-v\u00f5tme leidmiseks ava Tautulli veebileht ja navigeeri jaotisse Seaded ja seej\u00e4rel veebiliidesesse. API v\u00f5ti asub selle lehe allosas.", + "title": "Taastuvasta Tautulli" + }, + "user": { + "data": { + "api_key": "API v\u00f5ti", + "url": "URL", + "verify_ssl": "Kontrolli SSL sertifikaati" + }, + "description": "API-v\u00f5tme leidmiseks ava Tautulli veebileht ja navigeeri jaotisse Seaded ja seej\u00e4rel veebiliidesesse. API v\u00f5ti asub selle lehe allosas. \n\n URL-i n\u00e4ide: ```http://192.168.0.10:8181```, vaikeportiks on 8181." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/fr.json b/homeassistant/components/tautulli/translations/fr.json new file mode 100644 index 00000000000..05b66af0972 --- /dev/null +++ b/homeassistant/components/tautulli/translations/fr.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Cl\u00e9 d'API" + }, + "description": "Pour trouver votre cl\u00e9 d'API, ouvrez la page web de Tautulli et acc\u00e9dez aux param\u00e8tres puis \u00e0 l'interface web. La cl\u00e9 d'API se trouve au bas de cette page.", + "title": "R\u00e9-authentifier Tautulli" + }, + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "url": "URL", + "verify_ssl": "V\u00e9rifier le certificat SSL" + }, + "description": "Pour trouver votre cl\u00e9 d'API, ouvrez la page web de Tautulli et acc\u00e9dez aux param\u00e8tres puis \u00e0 l'interface web. La cl\u00e9 d'API se trouve au bas de cette page.\n\nExemple d'URL\u00a0: ```http://192.168.0.10:8181``` o\u00f9 8181 est le port par d\u00e9faut." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/hu.json b/homeassistant/components/tautulli/translations/hu.json new file mode 100644 index 00000000000..b654081ecd1 --- /dev/null +++ b/homeassistant/components/tautulli/translations/hu.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API kulcs" + }, + "description": "Az API-kulcs megkeres\u00e9s\u00e9hez nyissa meg a Tautulli weblapot, \u00e9s keresse meg a Be\u00e1ll\u00edt\u00e1sok, majd a webes fel\u00fcletet. Az API-kulcs az oldal alj\u00e1n lesz.", + "title": "Tautulli \u00fajrahiteles\u00edt\u00e9se" + }, + "user": { + "data": { + "api_key": "API kulcs", + "url": "URL", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "description": "Az API-kulcs megtal\u00e1l\u00e1s\u00e1hoz nyissa meg a Tautulli weboldalt, \u00e9s navig\u00e1ljon a Be\u00e1ll\u00edt\u00e1sok, majd a Webes fel\u00fcletre. Az API-kulcs az oldal alj\u00e1n tal\u00e1lhat\u00f3. \n\nP\u00e9lda az URL-re: ```http://192.168.0.10:8181```, ahol a 8181 az alap\u00e9rtelmezett port." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/id.json b/homeassistant/components/tautulli/translations/id.json new file mode 100644 index 00000000000..c2042dacffa --- /dev/null +++ b/homeassistant/components/tautulli/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Autentikasi ulang berhasil", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Kunci API" + }, + "description": "Untuk menemukan kunci API Anda, buka halaman web Tautulli dan navigasikan ke Pengaturan lalu ke antarmuka Web. Kunci API akan berada di bagian bawah halaman tersebut.", + "title": "Autentikasi Ulang Tautulli" + }, + "user": { + "data": { + "api_key": "Kunci API", + "url": "URL", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Untuk menemukan kunci API Anda, buka halaman web Tautulli dan navigasikan ke Pengaturan lalu ke antarmuka Web. Kunci API akan berada di bagian bawah halaman tersebut.\n\nContoh URL: ```http://192.168.0.10:8181``` dengan 8181 sebagai port default." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/it.json b/homeassistant/components/tautulli/translations/it.json new file mode 100644 index 00000000000..7dcfb1dd057 --- /dev/null +++ b/homeassistant/components/tautulli/translations/it.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chiave API" + }, + "description": "Per trovare la tua chiave API, apri la pagina web di Tautulli e vai su Impostazioni e poi su Interfaccia web. La chiave API sar\u00e0 in fondo a quella pagina.", + "title": "Autentica nuovamente Tautulli" + }, + "user": { + "data": { + "api_key": "Chiave API", + "url": "URL", + "verify_ssl": "Verifica il certificato SSL" + }, + "description": "Per trovare la tua chiave API, apri la pagina web di Tautulli e vai su Impostazioni e poi su Interfaccia web. La chiave API sar\u00e0 in fondo a quella pagina. \n\n Esempio dell'URL: ```http://192.168.0.10:8181``` con 8181 come porta predefinita." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/nl.json b/homeassistant/components/tautulli/translations/nl.json new file mode 100644 index 00000000000..eeecbd56477 --- /dev/null +++ b/homeassistant/components/tautulli/translations/nl.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "reauth_successful": "Herauthenticatie was succesvol", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-sleutel" + }, + "title": "Herauthenticeer Tautulli" + }, + "user": { + "data": { + "api_key": "API-sleutel", + "url": "URL", + "verify_ssl": "SSL-certificaat verifi\u00ebren" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/no.json b/homeassistant/components/tautulli/translations/no.json new file mode 100644 index 00000000000..cd6667b26fe --- /dev/null +++ b/homeassistant/components/tautulli/translations/no.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-n\u00f8kkel" + }, + "description": "For \u00e5 finne API-n\u00f8kkelen din, \u00e5pne Tautulli-nettsiden og naviger til Innstillinger og deretter til nettgrensesnitt. API-n\u00f8kkelen vil v\u00e6re nederst p\u00e5 siden.", + "title": "Autentiser Tautulli p\u00e5 nytt" + }, + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "url": "URL", + "verify_ssl": "Verifisere SSL-sertifikat" + }, + "description": "For \u00e5 finne API-n\u00f8kkelen din, \u00e5pne Tautulli-nettsiden og naviger til Innstillinger og deretter til nettgrensesnitt. API-n\u00f8kkelen vil v\u00e6re nederst p\u00e5 siden. \n\n Eksempel p\u00e5 URL: ```http://192.168.0.10:8181``` med 8181 som standardport." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/pl.json b/homeassistant/components/tautulli/translations/pl.json new file mode 100644 index 00000000000..25684ac6b3c --- /dev/null +++ b/homeassistant/components/tautulli/translations/pl.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Klucz API" + }, + "description": "Aby znale\u017a\u0107 klucz API, otw\u00f3rz stron\u0119 Tautulli i przejd\u017a do \"Settings\", a nast\u0119pnie do \"Web interface\". Klucz API b\u0119dzie znajdowa\u0107 si\u0119 na dole tej strony.", + "title": "Ponowne uwierzytelnienie Tautulli" + }, + "user": { + "data": { + "api_key": "Klucz API", + "url": "URL", + "verify_ssl": "Weryfikacja certyfikatu SSL" + }, + "description": "Aby znale\u017a\u0107 klucz API, otw\u00f3rz stron\u0119 Tautulli i przejd\u017a do \"Settings\", a nast\u0119pnie do \"Web interface\". Klucz API b\u0119dzie znajdowa\u0107 si\u0119 na dole tej strony. \n\nPrzyk\u0142ad adresu URL: \"http://192.168.0.10:8181\" z portem domy\u015blnym 8181." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/pt-BR.json b/homeassistant/components/tautulli/translations/pt-BR.json new file mode 100644 index 00000000000..45c5b508f96 --- /dev/null +++ b/homeassistant/components/tautulli/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida", + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + }, + "description": "Para encontrar sua chave de API, abra a p\u00e1gina da Web do Tautulli e navegue at\u00e9 Configura\u00e7\u00f5es e, em seguida, para a interface da Web. A chave da API estar\u00e1 na parte inferior dessa p\u00e1gina.", + "title": "Re-autenticar Tautulli" + }, + "user": { + "data": { + "api_key": "Chave da API", + "url": "URL", + "verify_ssl": "Verifique o certificado SSL" + }, + "description": "Para encontrar sua chave de API, abra a p\u00e1gina da Web do Tautulli e navegue at\u00e9 Configura\u00e7\u00f5es e, em seguida, para a interface da Web. A chave da API estar\u00e1 na parte inferior dessa p\u00e1gina. \n\n Exemplo da URL: ```http://192.168.0.10:8181``` com 8181 sendo a porta padr\u00e3o." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/ru.json b/homeassistant/components/tautulli/translations/ru.json new file mode 100644 index 00000000000..fc5e6584157 --- /dev/null +++ b/homeassistant/components/tautulli/translations/ru.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 Tautulli \u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u00ab\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u00bb, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 \u00ab\u0412\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u00bb. \u041a\u043b\u044e\u0447 API \u0431\u0443\u0434\u0435\u0442 \u0432\u043d\u0438\u0437\u0443 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u044f" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "url": "URL-\u0430\u0434\u0440\u0435\u0441", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + }, + "description": "\u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u0441\u0432\u043e\u0439 \u043a\u043b\u044e\u0447 API, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 Tautulli \u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u00ab\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u00bb, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432 \u00ab\u0412\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u00bb. \u041a\u043b\u044e\u0447 API \u0431\u0443\u0434\u0435\u0442 \u0432\u043d\u0438\u0437\u0443 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \n\n\u041f\u0440\u0438\u043c\u0435\u0440 URL-\u0430\u0434\u0440\u0435\u0441\u0430: ```http://192.168.0.10:8181```, \u0433\u0434\u0435 8181 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0440\u0442\u043e\u043c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tautulli/translations/zh-Hant.json b/homeassistant/components/tautulli/translations/zh-Hant.json new file mode 100644 index 00000000000..aed9ce361ee --- /dev/null +++ b/homeassistant/components/tautulli/translations/zh-Hant.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", + "single_instance_allowed": "\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3001\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API \u91d1\u9470" + }, + "description": "\u8981\u53d6\u5f97 API \u91d1\u9470\uff0c\u958b\u555f Tautulli \u7db2\u9801\u3001\u4e26\u700f\u89bd\u8a2d\u5b9a\u4e2d\u7684\u7db2\u7ad9\u4ecb\u9762\uff08Web interface\uff09\u3002\u53ef\u4ee5\u65bc\u9801\u9762\u4e0b\u65b9\u627e\u5230 API \u91d1\u9470\u3002", + "title": "\u91cd\u65b0\u8a8d\u8b49 Tautulli" + }, + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "url": "\u7db2\u5740", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + }, + "description": "\u8981\u53d6\u5f97 API \u91d1\u9470\uff0c\u958b\u555f Tautulli \u7db2\u9801\u3001\u4e26\u700f\u89bd\u8a2d\u5b9a\u4e2d\u7684\u7db2\u7ad9\u4ecb\u9762\uff08Web interface\uff09\u3002\u53ef\u4ee5\u65bc\u9801\u9762\u4e0b\u65b9\u627e\u5230 API \u91d1\u9470\u3002\n\nURL \u7bc4\u4f8b\uff1a```http://192.168.0.10:8181``` \u4e26\u4ee5 8181 \u70ba\u9810\u8a2d\u901a\u8a0a\u57e0\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py index a3965f520d5..7609cc6adee 100644 --- a/homeassistant/components/tellduslive/light.py +++ b/homeassistant/components/tellduslive/light.py @@ -2,11 +2,7 @@ import logging from homeassistant.components import light, tellduslive -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -39,6 +35,9 @@ async def async_setup_entry( class TelldusLiveLight(TelldusLiveEntity, LightEntity): """Representation of a Tellstick Net light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, client, device_id): """Initialize the Tellstick Net light.""" super().__init__(client, device_id) @@ -54,11 +53,6 @@ class TelldusLiveLight(TelldusLiveEntity, LightEntity): """Return the brightness of this light between 0..255.""" return self.device.dim_level - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - @property def is_on(self): """Return true if light is on.""" diff --git a/homeassistant/components/tellduslive/translations/hu.json b/homeassistant/components/tellduslive/translations/hu.json index a07259b67f9..2844415f630 100644 --- a/homeassistant/components/tellduslive/translations/hu.json +++ b/homeassistant/components/tellduslive/translations/hu.json @@ -11,7 +11,7 @@ }, "step": { "auth": { - "description": "A TelldusLive-fi\u00f3k \u00f6sszekapcsol\u00e1sa:\n 1. Kattintson az al\u00e1bbi linkre\n 2. Jelentkezzen be a Telldus Live szolg\u00e1ltat\u00e1sba\n 3. Enged\u00e9lyezzeie kell **{app_name}** (kattintson a ** Yes ** gombra).\n 4. J\u00f6jj\u00f6n vissza ide, \u00e9s kattintson a ** K\u00fcld\u00e9s ** gombra. \n\n [Link TelldusLive-fi\u00f3k]({auth_url})", + "description": "A TelldusLive-fi\u00f3k \u00f6sszekapcsol\u00e1sa:\n 1. Kattintson az al\u00e1bbi linkre\n 2. Jelentkezzen be a Telldus Live szolg\u00e1ltat\u00e1sba\n 3. Enged\u00e9lyezzeie kell **{app_name}** (kattintson a **Yes** gombra).\n 4. J\u00f6jj\u00f6n vissza ide, \u00e9s kattintson a **Mehet** gombra. \n\n [Link TelldusLive-fi\u00f3k]({auth_url})", "title": "Hiteles\u00edtsen a TelldusLive-on" }, "user": { diff --git a/homeassistant/components/tellstick/light.py b/homeassistant/components/tellstick/light.py index 5ead1bf6bda..136dd3ebbd6 100644 --- a/homeassistant/components/tellstick/light.py +++ b/homeassistant/components/tellstick/light.py @@ -1,11 +1,7 @@ """Support for Tellstick lights.""" from __future__ import annotations -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -18,8 +14,6 @@ from . import ( TellstickDevice, ) -SUPPORT_TELLSTICK = SUPPORT_BRIGHTNESS - def setup_platform( hass: HomeAssistant, @@ -47,6 +41,9 @@ def setup_platform( class TellstickLight(TellstickDevice, LightEntity): """Representation of a Tellstick light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, tellcore_device, signal_repetitions): """Initialize the Tellstick light.""" super().__init__(tellcore_device, signal_repetitions) @@ -58,11 +55,6 @@ class TellstickLight(TellstickDevice, LightEntity): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_TELLSTICK - def _parse_ha_data(self, kwargs): """Turn the value from HA into something useful.""" return kwargs.get(ATTR_BRIGHTNESS) diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index b1f1af4a6e0..9d81dce28fe 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -8,15 +8,10 @@ import voluptuous as vol from homeassistant.components.alarm_control_panel import ( ENTITY_ID_FORMAT, - FORMAT_NUMBER, - FORMAT_TEXT, PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.const import ( ATTR_CODE, @@ -64,12 +59,12 @@ CONF_CODE_ARM_REQUIRED = "code_arm_required" CONF_CODE_FORMAT = "code_format" -class CodeFormat(Enum): +class TemplateCodeFormat(Enum): """Class to represent different code formats.""" no_code = None - number = FORMAT_NUMBER - text = FORMAT_TEXT + number = CodeFormat.NUMBER + text = CodeFormat.TEXT ALARM_CONTROL_PANEL_SCHEMA = vol.Schema( @@ -80,8 +75,8 @@ ALARM_CONTROL_PANEL_SCHEMA = vol.Schema( vol.Optional(CONF_ARM_HOME_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_ARM_NIGHT_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, - vol.Optional(CONF_CODE_FORMAT, default=CodeFormat.number.name): cv.enum( - CodeFormat + vol.Optional(CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name): cv.enum( + TemplateCodeFormat ), vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, @@ -173,13 +168,19 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity): """Return the list of supported features.""" supported_features = 0 if self._arm_night_script is not None: - supported_features = supported_features | SUPPORT_ALARM_ARM_NIGHT + supported_features = ( + supported_features | AlarmControlPanelEntityFeature.ARM_NIGHT + ) if self._arm_home_script is not None: - supported_features = supported_features | SUPPORT_ALARM_ARM_HOME + supported_features = ( + supported_features | AlarmControlPanelEntityFeature.ARM_HOME + ) if self._arm_away_script is not None: - supported_features = supported_features | SUPPORT_ALARM_ARM_AWAY + supported_features = ( + supported_features | AlarmControlPanelEntityFeature.ARM_AWAY + ) return supported_features @@ -206,8 +207,9 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity): return _LOGGER.error( - "Received invalid alarm panel state: %s. Expected: %s", + "Received invalid alarm panel state: %s for entity %s. Expected: %s", result, + self.entity_id, ", ".join(_VALID_STATES), ) self._state = None diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 6c1bad3124e..e1df61bf4a2 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -11,15 +11,8 @@ from homeassistant.components.cover import ( DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverEntity, + CoverEntityFeature, ) from homeassistant.const import ( CONF_COVERS, @@ -71,10 +64,10 @@ CONF_TILT_OPTIMISTIC = "tilt_optimistic" CONF_OPEN_AND_CLOSE = "open_or_close" TILT_FEATURES = ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_STOP_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION ) COVER_SCHEMA = vol.All( @@ -229,8 +222,9 @@ class CoverTemplate(TemplateEntity, CoverEntity): self._is_closing = state == STATE_CLOSING else: _LOGGER.error( - "Received invalid cover is_on state: %s. Expected: %s", + "Received invalid cover is_on state: %s for entity %s. Expected: %s", state, + self.entity_id, ", ".join(_VALID_STATES), ) if not self._position_template: @@ -313,13 +307,13 @@ class CoverTemplate(TemplateEntity, CoverEntity): @property def supported_features(self): """Flag supported features.""" - supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE if self._stop_script is not None: - supported_features |= SUPPORT_STOP + supported_features |= CoverEntityFeature.STOP if self._position_script is not None: - supported_features |= SUPPORT_SET_POSITION + supported_features |= CoverEntityFeature.SET_POSITION if self._tilt_script is not None: supported_features |= TILT_FEATURES diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index b172e94016f..6b0fdefc2f9 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -14,11 +14,8 @@ from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, ENTITY_ID_FORMAT, - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.const import ( CONF_ENTITY_ID, @@ -199,13 +196,13 @@ class TemplateFan(TemplateEntity, FanEntity): self._preset_modes = config.get(CONF_PRESET_MODES) if self._percentage_template: - self._supported_features |= SUPPORT_SET_SPEED + self._supported_features |= FanEntityFeature.SET_SPEED if self._preset_mode_template and self._preset_modes: - self._supported_features |= SUPPORT_PRESET_MODE + self._supported_features |= FanEntityFeature.PRESET_MODE if self._oscillating_template: - self._supported_features |= SUPPORT_OSCILLATE + self._supported_features |= FanEntityFeature.OSCILLATE if self._direction_template: - self._supported_features |= SUPPORT_DIRECTION + self._supported_features |= FanEntityFeature.DIRECTION @property def supported_features(self) -> int: @@ -288,8 +285,9 @@ class TemplateFan(TemplateEntity, FanEntity): """Set the preset_mode of the fan.""" if self.preset_modes and preset_mode not in self.preset_modes: _LOGGER.error( - "Received invalid preset_mode: %s. Expected: %s", + "Received invalid preset_mode: %s for entity %s. Expected: %s", preset_mode, + self.entity_id, self.preset_modes, ) return @@ -325,8 +323,9 @@ class TemplateFan(TemplateEntity, FanEntity): ) else: _LOGGER.error( - "Received invalid direction: %s. Expected: %s", + "Received invalid direction: %s for entity %s. Expected: %s", direction, + self.entity_id, ", ".join(_VALID_DIRECTIONS), ) @@ -344,8 +343,9 @@ class TemplateFan(TemplateEntity, FanEntity): self._state = None else: _LOGGER.error( - "Received invalid fan is_on state: %s. Expected: %s", + "Received invalid fan is_on state: %s for entity %s. Expected: %s", result, + self.entity_id, ", ".join(_VALID_STATES), ) self._state = None @@ -393,7 +393,11 @@ class TemplateFan(TemplateEntity, FanEntity): try: percentage = int(float(percentage)) except ValueError: - _LOGGER.error("Received invalid percentage: %s", percentage) + _LOGGER.error( + "Received invalid percentage: %s for entity %s", + percentage, + self.entity_id, + ) self._percentage = 0 self._preset_mode = None return @@ -402,7 +406,11 @@ class TemplateFan(TemplateEntity, FanEntity): self._percentage = percentage self._preset_mode = None else: - _LOGGER.error("Received invalid percentage: %s", percentage) + _LOGGER.error( + "Received invalid percentage: %s for entity %s", + percentage, + self.entity_id, + ) self._percentage = 0 self._preset_mode = None @@ -419,8 +427,9 @@ class TemplateFan(TemplateEntity, FanEntity): self._preset_mode = None else: _LOGGER.error( - "Received invalid preset_mode: %s. Expected: %s", + "Received invalid preset_mode: %s for entity %s. Expected: %s", preset_mode, + self.entity_id, self.preset_mode, ) self._percentage = None @@ -437,8 +446,9 @@ class TemplateFan(TemplateEntity, FanEntity): self._oscillating = None else: _LOGGER.error( - "Received invalid oscillating: %s. Expected: True/False", + "Received invalid oscillating: %s for entity %s. Expected: True/False", oscillating, + self.entity_id, ) self._oscillating = None @@ -451,8 +461,9 @@ class TemplateFan(TemplateEntity, FanEntity): self._direction = None else: _LOGGER.error( - "Received invalid direction: %s. Expected: %s", + "Received invalid direction: %s for entity %s. Expected: %s", direction, + self.entity_id, ", ".join(_VALID_DIRECTIONS), ) self._direction = None diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index d2260aa10dc..5a79b3db8fc 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -393,8 +393,9 @@ class LightTemplate(TemplateEntity, LightEntity): effect = kwargs[ATTR_EFFECT] if effect not in self._effect_list: _LOGGER.error( - "Received invalid effect: %s. Expected one of: %s", + "Received invalid effect: %s for entity %s. Expected one of: %s", effect, + self.entity_id, self._effect_list, exc_info=True, ) @@ -443,7 +444,9 @@ class LightTemplate(TemplateEntity, LightEntity): self._brightness = int(brightness) else: _LOGGER.error( - "Received invalid brightness : %s. Expected: 0-255", brightness + "Received invalid brightness : %s for entity %s. Expected: 0-255", + brightness, + self.entity_id, ) self._brightness = None except ValueError: @@ -464,7 +467,9 @@ class LightTemplate(TemplateEntity, LightEntity): self._white_value = int(white_value) else: _LOGGER.error( - "Received invalid white value: %s. Expected: 0-255", white_value + "Received invalid white value: %s for entity %s. Expected: 0-255", + white_value, + self.entity_id, ) self._white_value = None except ValueError: @@ -483,8 +488,9 @@ class LightTemplate(TemplateEntity, LightEntity): if not isinstance(effect_list, list): _LOGGER.error( - "Received invalid effect list: %s. Expected list of strings", + "Received invalid effect list: %s for entity %s. Expected list of strings", effect_list, + self.entity_id, ) self._effect_list = None return @@ -504,8 +510,9 @@ class LightTemplate(TemplateEntity, LightEntity): if effect not in self._effect_list: _LOGGER.error( - "Received invalid effect: %s. Expected one of: %s", + "Received invalid effect: %s for entity %s. Expected one of: %s", effect, + self.entity_id, self._effect_list, ) self._effect = None @@ -533,8 +540,9 @@ class LightTemplate(TemplateEntity, LightEntity): return _LOGGER.error( - "Received invalid light is_on state: %s. Expected: %s", + "Received invalid light is_on state: %s for entity %s. Expected: %s", state, + self.entity_id, ", ".join(_VALID_STATES), ) self._state = None @@ -551,8 +559,9 @@ class LightTemplate(TemplateEntity, LightEntity): self._temperature = temperature else: _LOGGER.error( - "Received invalid color temperature : %s. Expected: %s-%s", + "Received invalid color temperature : %s for entity %s. Expected: %s-%s", temperature, + self.entity_id, self.min_mireds, self.max_mireds, ) @@ -591,13 +600,16 @@ class LightTemplate(TemplateEntity, LightEntity): self._color = (h_str, s_str) elif h_str is not None and s_str is not None: _LOGGER.error( - "Received invalid hs_color : (%s, %s). Expected: (0-360, 0-100)", + "Received invalid hs_color : (%s, %s) for entity %s. Expected: (0-360, 0-100)", h_str, s_str, + self.entity_id, ) self._color = None else: - _LOGGER.error("Received invalid hs_color : (%s)", render) + _LOGGER.error( + "Received invalid hs_color : (%s) for entity %s", render, self.entity_id + ) self._color = None @callback diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index a61d15cb7a4..126dd551c45 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -13,6 +13,7 @@ from homeassistant.components.sensor import ( ENTITY_ID_FORMAT, PLATFORM_SCHEMA, STATE_CLASSES_SCHEMA, + RestoreSensor, SensorDeviceClass, SensorEntity, ) @@ -30,6 +31,8 @@ from homeassistant.const import ( CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import TemplateError @@ -237,12 +240,26 @@ class SensorTemplate(TemplateEntity, SensorEntity): ) -class TriggerSensorEntity(TriggerEntity, SensorEntity): +class TriggerSensorEntity(TriggerEntity, RestoreSensor): """Sensor entity based on trigger data.""" domain = SENSOR_DOMAIN extra_template_keys = (CONF_STATE,) + async def async_added_to_hass(self) -> None: + """Restore last state.""" + await super().async_added_to_hass() + if ( + (last_state := await self.async_get_last_state()) is not None + and (extra_data := await self.async_get_last_sensor_data()) is not None + and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) + # The trigger might have fired already while we waited for stored data, + # then we should not restore state + and CONF_STATE not in self._rendered + ): + self._rendered[CONF_STATE] = extra_data.native_value + self.restore_attributes(last_state) + @property def native_value(self) -> str | datetime | date | None: """Return state of the sensor.""" diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 5e592e5d717..a6d1cba78e1 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -17,8 +17,9 @@ from homeassistant.const import ( CONF_ICON_TEMPLATE, CONF_NAME, EVENT_HOMEASSISTANT_START, + STATE_UNKNOWN, ) -from homeassistant.core import CoreState, Event, callback +from homeassistant.core import CoreState, Event, State, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -27,7 +28,11 @@ from homeassistant.helpers.event import ( TrackTemplateResult, async_track_template_result, ) -from homeassistant.helpers.template import Template, result_as_boolean +from homeassistant.helpers.template import ( + Template, + TemplateStateFromEntityId, + result_as_boolean, +) from .const import ( CONF_ATTRIBUTE_TEMPLATES, @@ -247,13 +252,28 @@ class TemplateEntity(Entity): self._entity_picture_template = config.get(CONF_PICTURE) self._friendly_name_template = config.get(CONF_NAME) + class DummyState(State): + """None-state for template entities not yet added to the state machine.""" + + def __init__(self) -> None: + """Initialize a new state.""" + super().__init__("unknown.unknown", STATE_UNKNOWN) + self.entity_id = None # type: ignore[assignment] + + @property + def name(self) -> str: + """Name of this state.""" + return "" + + variables = {"this": DummyState()} + # Try to render the name as it can influence the entity ID self._attr_name = fallback_name if self._friendly_name_template: self._friendly_name_template.hass = hass with contextlib.suppress(TemplateError): self._attr_name = self._friendly_name_template.async_render( - parse_result=False + variables=variables, parse_result=False ) # Templates will not render while the entity is unavailable, try to render the @@ -262,13 +282,15 @@ class TemplateEntity(Entity): self._entity_picture_template.hass = hass with contextlib.suppress(TemplateError): self._attr_entity_picture = self._entity_picture_template.async_render( - parse_result=False + variables=variables, parse_result=False ) if self._icon_template: self._icon_template.hass = hass with contextlib.suppress(TemplateError): - self._attr_icon = self._icon_template.async_render(parse_result=False) + self._attr_icon = self._icon_template.async_render( + variables=variables, parse_result=False + ) @callback def _update_available(self, result): @@ -368,8 +390,11 @@ class TemplateEntity(Entity): async def _async_template_startup(self, *_) -> None: template_var_tups: list[TrackTemplate] = [] has_availability_template = False + + variables = {"this": TemplateStateFromEntityId(self.hass, self.entity_id)} + for template, attributes in self._template_attrs.items(): - template_var_tup = TrackTemplate(template, None) + template_var_tup = TrackTemplate(template, variables) is_availability_template = False for attribute in attributes: # pylint: disable-next=protected-access diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index f76c64f739a..1d350d120c7 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -21,16 +21,8 @@ from homeassistant.components.vacuum import ( STATE_IDLE, STATE_PAUSED, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STOP, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.const import ( CONF_ENTITY_ID, @@ -153,83 +145,63 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity): self._template = config.get(CONF_VALUE_TEMPLATE) self._battery_level_template = config.get(CONF_BATTERY_LEVEL_TEMPLATE) self._fan_speed_template = config.get(CONF_FAN_SPEED_TEMPLATE) - self._supported_features = SUPPORT_START + self._attr_supported_features = VacuumEntityFeature.START self._start_script = Script(hass, config[SERVICE_START], friendly_name, DOMAIN) self._pause_script = None if pause_action := config.get(SERVICE_PAUSE): self._pause_script = Script(hass, pause_action, friendly_name, DOMAIN) - self._supported_features |= SUPPORT_PAUSE + self._attr_supported_features |= VacuumEntityFeature.PAUSE self._stop_script = None if stop_action := config.get(SERVICE_STOP): self._stop_script = Script(hass, stop_action, friendly_name, DOMAIN) - self._supported_features |= SUPPORT_STOP + self._attr_supported_features |= VacuumEntityFeature.STOP self._return_to_base_script = None if return_to_base_action := config.get(SERVICE_RETURN_TO_BASE): self._return_to_base_script = Script( hass, return_to_base_action, friendly_name, DOMAIN ) - self._supported_features |= SUPPORT_RETURN_HOME + self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME self._clean_spot_script = None if clean_spot_action := config.get(SERVICE_CLEAN_SPOT): self._clean_spot_script = Script( hass, clean_spot_action, friendly_name, DOMAIN ) - self._supported_features |= SUPPORT_CLEAN_SPOT + self._attr_supported_features |= VacuumEntityFeature.CLEAN_SPOT self._locate_script = None if locate_action := config.get(SERVICE_LOCATE): self._locate_script = Script(hass, locate_action, friendly_name, DOMAIN) - self._supported_features |= SUPPORT_LOCATE + self._attr_supported_features |= VacuumEntityFeature.LOCATE self._set_fan_speed_script = None if set_fan_speed_action := config.get(SERVICE_SET_FAN_SPEED): self._set_fan_speed_script = Script( hass, set_fan_speed_action, friendly_name, DOMAIN ) - self._supported_features |= SUPPORT_FAN_SPEED + self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED self._state = None self._battery_level = None - self._fan_speed = None + self._attr_fan_speed = None if self._template: - self._supported_features |= SUPPORT_STATE + self._attr_supported_features |= VacuumEntityFeature.STATE if self._battery_level_template: - self._supported_features |= SUPPORT_BATTERY + self._attr_supported_features |= VacuumEntityFeature.BATTERY # List of valid fan speeds - self._fan_speed_list = config[CONF_FAN_SPEED_LIST] - - @property - def supported_features(self) -> int: - """Flag supported features.""" - return self._supported_features + self._attr_fan_speed_list = config[CONF_FAN_SPEED_LIST] @property def state(self): """Return the status of the vacuum cleaner.""" return self._state - @property - def battery_level(self): - """Return the battery level of the vacuum cleaner.""" - return self._battery_level - - @property - def fan_speed(self): - """Return the fan speed of the vacuum cleaner.""" - return self._fan_speed - - @property - def fan_speed_list(self) -> list: - """Get the list of available fan speeds.""" - return self._fan_speed_list - async def async_start(self): """Start or resume the cleaning task.""" await self._start_script.async_run(context=self._context) @@ -274,16 +246,17 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity): if self._set_fan_speed_script is None: return - if fan_speed in self._fan_speed_list: - self._fan_speed = fan_speed + if fan_speed in self._attr_fan_speed_list: + self._attr_fan_speed = fan_speed await self._set_fan_speed_script.async_run( {ATTR_FAN_SPEED: fan_speed}, context=self._context ) else: _LOGGER.error( - "Received invalid fan speed: %s. Expected: %s", + "Received invalid fan speed: %s for entity %s. Expected: %s", fan_speed, - self._fan_speed_list, + self.entity_id, + self._attr_fan_speed_list, ) async def async_added_to_hass(self): @@ -326,8 +299,9 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity): self._state = None else: _LOGGER.error( - "Received invalid vacuum state: %s. Expected: %s", + "Received invalid vacuum state: %s for entity %s. Expected: %s", result, + self.entity_id, ", ".join(_VALID_STATES), ) self._state = None @@ -340,29 +314,32 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity): raise ValueError except ValueError: _LOGGER.error( - "Received invalid battery level: %s. Expected: 0-100", battery_level + "Received invalid battery level: %s for entity %s. Expected: 0-100", + battery_level, + self.entity_id, ) - self._battery_level = None + self._attr_battery_level = None return - self._battery_level = battery_level_int + self._attr_battery_level = battery_level_int @callback def _update_fan_speed(self, fan_speed): if isinstance(fan_speed, TemplateError): # This is legacy behavior - self._fan_speed = None + self._attr_fan_speed = None self._state = None return - if fan_speed in self._fan_speed_list: - self._fan_speed = fan_speed + if fan_speed in self._attr_fan_speed_list: + self._attr_fan_speed = fan_speed elif fan_speed == STATE_UNKNOWN: - self._fan_speed = None + self._attr_fan_speed = None else: _LOGGER.error( - "Received invalid fan speed: %s. Expected: %s", + "Received invalid fan speed: %s for entity %s. Expected: %s", fan_speed, - self._fan_speed_list, + self.entity_id, + self._attr_fan_speed_list, ) - self._fan_speed = None + self._attr_fan_speed = None diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 6d8f50c81bf..5f1ac406b70 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.5.0", "pycocotools==2.0.1", "numpy==1.21.4", - "pillow==9.0.1" + "pillow==9.1.0" ], "codeowners": [], "iot_class": "local_polling", diff --git a/homeassistant/components/tesla_wall_connector/translations/ca.json b/homeassistant/components/tesla_wall_connector/translations/ca.json index 7f0056f3413..ff92037845f 100644 --- a/homeassistant/components/tesla_wall_connector/translations/ca.json +++ b/homeassistant/components/tesla_wall_connector/translations/ca.json @@ -16,15 +16,5 @@ "title": "Configuraci\u00f3 de Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Freq\u00fc\u00e8ncia d'actualitzaci\u00f3" - }, - "title": "Configuraci\u00f3 d'opcions de Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/de.json b/homeassistant/components/tesla_wall_connector/translations/de.json index 3500fc1aa7b..3fac77da9b1 100644 --- a/homeassistant/components/tesla_wall_connector/translations/de.json +++ b/homeassistant/components/tesla_wall_connector/translations/de.json @@ -16,15 +16,5 @@ "title": "Tesla Wall Connector konfigurieren" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Aktualisierungsfrequenz" - }, - "title": "Optionen f\u00fcr Tesla Wall Connector konfigurieren" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/el.json b/homeassistant/components/tesla_wall_connector/translations/el.json index 3a835ab5d57..c007cd83f2a 100644 --- a/homeassistant/components/tesla_wall_connector/translations/el.json +++ b/homeassistant/components/tesla_wall_connector/translations/el.json @@ -16,15 +16,5 @@ "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "\u03a3\u03c5\u03c7\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2" - }, - "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/en.json b/homeassistant/components/tesla_wall_connector/translations/en.json index 79a3005299f..436cc06ffb4 100644 --- a/homeassistant/components/tesla_wall_connector/translations/en.json +++ b/homeassistant/components/tesla_wall_connector/translations/en.json @@ -16,15 +16,5 @@ "title": "Configure Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Update frequency" - }, - "title": "Configure options for Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/es.json b/homeassistant/components/tesla_wall_connector/translations/es.json index 0e531ec1c01..34cdf425528 100644 --- a/homeassistant/components/tesla_wall_connector/translations/es.json +++ b/homeassistant/components/tesla_wall_connector/translations/es.json @@ -16,15 +16,5 @@ "title": "Configurar el conector de pared Tesla" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Frecuencia de actualizaci\u00f3n" - }, - "title": "Configurar las opciones del conector de pared Tesla" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/et.json b/homeassistant/components/tesla_wall_connector/translations/et.json index a13b447d542..877e80759e6 100644 --- a/homeassistant/components/tesla_wall_connector/translations/et.json +++ b/homeassistant/components/tesla_wall_connector/translations/et.json @@ -16,15 +16,5 @@ "title": "Tesla Wall Connector'i seadistamine" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "V\u00e4rskendussagedus" - }, - "title": "Tesla Wall Connector'i seadistamise valikud" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/fr.json b/homeassistant/components/tesla_wall_connector/translations/fr.json index 6cf4aacf7d3..679b5f4b489 100644 --- a/homeassistant/components/tesla_wall_connector/translations/fr.json +++ b/homeassistant/components/tesla_wall_connector/translations/fr.json @@ -16,15 +16,5 @@ "title": "Configurer le connecteur mural Tesla" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Fr\u00e9quence de mise \u00e0 jour" - }, - "title": "Configurer les options pour le connecteur mural Tesla" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/hu.json b/homeassistant/components/tesla_wall_connector/translations/hu.json index 951d4de5a86..ef490721c43 100644 --- a/homeassistant/components/tesla_wall_connector/translations/hu.json +++ b/homeassistant/components/tesla_wall_connector/translations/hu.json @@ -16,15 +16,5 @@ "title": "Tesla Wall Connector konfigur\u00e1l\u00e1sa" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Friss\u00edt\u00e9si gyakoris\u00e1g" - }, - "title": "Tesla Wall Connector konfigur\u00e1l\u00e1sa" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/id.json b/homeassistant/components/tesla_wall_connector/translations/id.json index 6214e3d153f..3ff19213a6c 100644 --- a/homeassistant/components/tesla_wall_connector/translations/id.json +++ b/homeassistant/components/tesla_wall_connector/translations/id.json @@ -16,15 +16,5 @@ "title": "Konfigurasikan Konektor Dinding Tesla" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Frekuensi pembaruan" - }, - "title": "Konfigurasikan opsi untuk Konektor Dinding Tesla" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/it.json b/homeassistant/components/tesla_wall_connector/translations/it.json index 5f79a0ee9e0..c67ff4c8cd0 100644 --- a/homeassistant/components/tesla_wall_connector/translations/it.json +++ b/homeassistant/components/tesla_wall_connector/translations/it.json @@ -16,15 +16,5 @@ "title": "Configura Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Frequenza di aggiornamento" - }, - "title": "Configura le opzioni per Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/ja.json b/homeassistant/components/tesla_wall_connector/translations/ja.json index 5370b0b161a..2eb8c3e695b 100644 --- a/homeassistant/components/tesla_wall_connector/translations/ja.json +++ b/homeassistant/components/tesla_wall_connector/translations/ja.json @@ -16,15 +16,5 @@ "title": "Tesla Wall Connector\u306e\u8a2d\u5b9a" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "\u66f4\u65b0\u983b\u5ea6" - }, - "title": "Tesla Wall Connector\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/nl.json b/homeassistant/components/tesla_wall_connector/translations/nl.json index b6182cdcf5a..f766433231b 100644 --- a/homeassistant/components/tesla_wall_connector/translations/nl.json +++ b/homeassistant/components/tesla_wall_connector/translations/nl.json @@ -16,15 +16,5 @@ "title": "Configureer Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Update frequentie" - }, - "title": "Configureer opties voor Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/no.json b/homeassistant/components/tesla_wall_connector/translations/no.json index ed6b4c30cfd..7c5c10de679 100644 --- a/homeassistant/components/tesla_wall_connector/translations/no.json +++ b/homeassistant/components/tesla_wall_connector/translations/no.json @@ -16,15 +16,5 @@ "title": "Konfigurer Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Oppdateringsfrekvens" - }, - "title": "Konfigurer alternativer for Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/pl.json b/homeassistant/components/tesla_wall_connector/translations/pl.json index 6ee749485ee..59ff982a024 100644 --- a/homeassistant/components/tesla_wall_connector/translations/pl.json +++ b/homeassistant/components/tesla_wall_connector/translations/pl.json @@ -16,15 +16,5 @@ "title": "Konfiguracja z\u0142\u0105cza \u015bciennego Tesla" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji" - }, - "title": "Konfiguracja opcji dla z\u0142\u0105cza \u015bciennego Tesla" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/pt-BR.json b/homeassistant/components/tesla_wall_connector/translations/pt-BR.json index da2c9d50f34..8b38c7654e3 100644 --- a/homeassistant/components/tesla_wall_connector/translations/pt-BR.json +++ b/homeassistant/components/tesla_wall_connector/translations/pt-BR.json @@ -16,15 +16,5 @@ "title": "Configurar o conector de parede Tesla" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "Frequ\u00eancia de atualiza\u00e7\u00e3o" - }, - "title": "Configurar op\u00e7\u00f5es para o Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/ru.json b/homeassistant/components/tesla_wall_connector/translations/ru.json index 0fc1ef88790..7af1aa27a6b 100644 --- a/homeassistant/components/tesla_wall_connector/translations/ru.json +++ b/homeassistant/components/tesla_wall_connector/translations/ru.json @@ -16,15 +16,5 @@ "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Tesla Wall Connector" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f" - }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Tesla Wall Connector" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/tr.json b/homeassistant/components/tesla_wall_connector/translations/tr.json index b409c4ddc33..89d926e7eab 100644 --- a/homeassistant/components/tesla_wall_connector/translations/tr.json +++ b/homeassistant/components/tesla_wall_connector/translations/tr.json @@ -16,15 +16,5 @@ "title": "Tesla Duvar Ba\u011flant\u0131s\u0131n\u0131 Yap\u0131land\u0131r\u0131n" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "G\u00fcncelleme s\u0131kl\u0131\u011f\u0131" - }, - "title": "Tesla Duvar Konekt\u00f6r\u00fc i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/zh-Hans.json b/homeassistant/components/tesla_wall_connector/translations/zh-Hans.json index 21ee02edbf0..22533320802 100644 --- a/homeassistant/components/tesla_wall_connector/translations/zh-Hans.json +++ b/homeassistant/components/tesla_wall_connector/translations/zh-Hans.json @@ -16,15 +16,5 @@ "title": "\u914d\u7f6e Tesla \u58c1\u6302\u5f0f\u5145\u7535\u8fde\u63a5\u5668" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "\u66f4\u65b0\u9891\u7387" - }, - "title": "Tesla \u58c1\u6302\u5f0f\u5145\u7535\u8fde\u63a5\u5668\u914d\u7f6e\u9009\u9879" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tesla_wall_connector/translations/zh-Hant.json b/homeassistant/components/tesla_wall_connector/translations/zh-Hant.json index 69de7dd2eb3..7e1a967689c 100644 --- a/homeassistant/components/tesla_wall_connector/translations/zh-Hant.json +++ b/homeassistant/components/tesla_wall_connector/translations/zh-Hant.json @@ -16,15 +16,5 @@ "title": "\u8a2d\u5b9a\u7279\u65af\u62c9\u58c1\u639b\u5f0f\u5145\u96fb\u5ea7" } } - }, - "options": { - "step": { - "init": { - "data": { - "scan_interval": "\u66f4\u65b0\u983b\u7387" - }, - "title": "\u7279\u65af\u62c9\u58c1\u639b\u5f0f\u5145\u96fb\u5ea7\u8a2d\u5b9a\u9078\u9805" - } - } } } \ No newline at end of file diff --git a/homeassistant/components/tfiac/climate.py b/homeassistant/components/tfiac/climate.py index b0fe1422681..937723440f5 100644 --- a/homeassistant/components/tfiac/climate.py +++ b/homeassistant/components/tfiac/climate.py @@ -14,19 +14,12 @@ from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, FAN_MEDIUM, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_VERTICAL, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant @@ -44,12 +37,12 @@ MIN_TEMP = 61 MAX_TEMP = 88 HVAC_MAP = { - HVAC_MODE_HEAT: "heat", - HVAC_MODE_AUTO: "selfFeel", - HVAC_MODE_DRY: "dehumi", - HVAC_MODE_FAN_ONLY: "fan", - HVAC_MODE_COOL: "cool", - HVAC_MODE_OFF: "off", + HVACMode.HEAT: "heat", + HVACMode.AUTO: "selfFeel", + HVACMode.DRY: "dehumi", + HVACMode.FAN_ONLY: "fan", + HVACMode.COOL: "cool", + HVACMode.OFF: "off", } HVAC_MAP_REV = {v: k for k, v in HVAC_MAP.items()} @@ -57,8 +50,6 @@ HVAC_MAP_REV = {v: k for k, v in HVAC_MAP.items()} SUPPORT_FAN = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW] SUPPORT_SWING = [SWING_OFF, SWING_HORIZONTAL, SWING_VERTICAL, SWING_BOTH] -SUPPORT_FLAGS = SUPPORT_FAN_MODE | SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE - CURR_TEMP = "current_temp" TARGET_TEMP = "target_temp" OPERATION_MODE = "operation" @@ -86,6 +77,12 @@ async def async_setup_platform( class TfiacClimate(ClimateEntity): """TFIAC class.""" + _attr_supported_features = ( + ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.SWING_MODE + | ClimateEntityFeature.TARGET_TEMPERATURE + ) + def __init__(self, hass, client): """Init class.""" self._client = client @@ -104,11 +101,6 @@ class TfiacClimate(ClimateEntity): except futures.TimeoutError: self._available = False - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - @property def min_temp(self): """Return the minimum temperature.""" @@ -140,19 +132,19 @@ class TfiacClimate(ClimateEntity): return self._client.status["current_temp"] @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode | None: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ if self._client.status[ON_MODE] != "on": - return HVAC_MODE_OFF + return HVACMode.OFF state = self._client.status["operation"] return HVAC_MAP_REV.get(state) @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. @@ -184,9 +176,9 @@ class TfiacClimate(ClimateEntity): if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None: await self._client.set_state(TARGET_TEMP, temp) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._client.set_state(ON_MODE, "off") else: await self._client.set_state(OPERATION_MODE, HVAC_MAP[hvac_mode]) diff --git a/homeassistant/components/threshold/config_flow.py b/homeassistant/components/threshold/config_flow.py index c77d4b57115..1e6236259bd 100644 --- a/homeassistant/components/threshold/config_flow.py +++ b/homeassistant/components/threshold/config_flow.py @@ -27,19 +27,25 @@ def _validate_mode(data: Any) -> Any: OPTIONS_SCHEMA = vol.Schema( { - vol.Required(CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): selector.selector( - {"number": {"mode": "box"}} + vol.Required( + CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS + ): selector.NumberSelector( + selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX), + ), + vol.Optional(CONF_LOWER): selector.NumberSelector( + selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX), + ), + vol.Optional(CONF_UPPER): selector.NumberSelector( + selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX), ), - vol.Optional(CONF_LOWER): selector.selector({"number": {"mode": "box"}}), - vol.Optional(CONF_UPPER): selector.selector({"number": {"mode": "box"}}), } ) CONFIG_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): selector.selector({"text": {}}), - vol.Required(CONF_ENTITY_ID): selector.selector( - {"entity": {"domain": "sensor"}} + vol.Required(CONF_NAME): selector.TextSelector(), + vol.Required(CONF_ENTITY_ID): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor") ), } ).extend(OPTIONS_SCHEMA.schema) diff --git a/homeassistant/components/threshold/translations/bg.json b/homeassistant/components/threshold/translations/bg.json new file mode 100644 index 00000000000..05bbb89437a --- /dev/null +++ b/homeassistant/components/threshold/translations/bg.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "data": { + "hysteresis": "\u0425\u0438\u0441\u0442\u0435\u0440\u0435\u0437\u0438\u0441", + "name": "\u0418\u043c\u0435" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "hysteresis": "\u0425\u0438\u0441\u0442\u0435\u0440\u0435\u0437\u0438\u0441", + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/ca.json b/homeassistant/components/threshold/translations/ca.json new file mode 100644 index 00000000000..b33bd8a04ed --- /dev/null +++ b/homeassistant/components/threshold/translations/ca.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Els l\u00edmits inferior i superior no poden estar tots dos buits" + }, + "step": { + "user": { + "data": { + "entity_id": "Sensor d'entrada", + "hysteresis": "Hist\u00e8resi", + "lower": "L\u00edmit inferior", + "mode": "Mode llindar", + "name": "Nom", + "upper": "L\u00edmit superior" + }, + "description": "Crea un sensor binari que s'activa o es desactiva en funci\u00f3 del valor d'un sensor\n\nNom\u00e9s amb l\u00edmit inferior configurat - S'activa quan el valor del sensor d'entrada est\u00e0 per sota del l\u00edmit.\nNom\u00e9s amb l\u00edmit superior configurat - S'activa quan el valor del sensor d'entrada est\u00e0 per sobre del l\u00edmit.\nAmbd\u00f3s l\u00edmits configurats - S'activa quan el valor del sensor d'entrada est\u00e0 dins l'interval entre els l\u00edmits [inferior .. superior].", + "title": "Afegeix sensor de llindar" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Els l\u00edmits inferior i superior no poden estar tots dos buits" + }, + "step": { + "init": { + "data": { + "entity_id": "Sensor d'entrada", + "hysteresis": "Hist\u00e8resi", + "lower": "L\u00edmit inferior", + "mode": "Mode llindar", + "name": "Nom", + "upper": "L\u00edmit superior" + }, + "description": "Nom\u00e9s amb l\u00edmit inferior configurat - S'activa quan el valor del sensor d'entrada est\u00e0 per sota del l\u00edmit.\nNom\u00e9s amb l\u00edmit superior configurat - S'activa quan el valor del sensor d'entrada est\u00e0 per sobre del l\u00edmit.\nAmbd\u00f3s l\u00edmits configurats - S'activa quan el valor del sensor d'entrada est\u00e0 dins l'interval entre els l\u00edmits [inferior .. superior]." + } + } + }, + "title": "Sensor de llindar" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/de.json b/homeassistant/components/threshold/translations/de.json new file mode 100644 index 00000000000..579dd4f49b0 --- /dev/null +++ b/homeassistant/components/threshold/translations/de.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Untere und obere Grenze k\u00f6nnen nicht beide leer sein" + }, + "step": { + "user": { + "data": { + "entity_id": "Eingangssensor", + "hysteresis": "Hysterese", + "lower": "Untere Grenze", + "mode": "Schwellenwertmodus", + "name": "Name", + "upper": "Obergrenze" + }, + "description": "Erstellen eines bin\u00e4ren Sensors, der sich je nach Wert eines Sensors ein- und ausschaltet\n\nNur unterer Grenzwert konfiguriert - Einschalten, wenn der Wert des Eingangssensors kleiner als der untere Grenzwert ist.\nNur oberer Grenzwert konfiguriert - Einschalten, wenn der Wert des Eingangssensors gr\u00f6\u00dfer als der obere Grenzwert ist.\nSowohl untere als auch obere Grenze konfiguriert - Einschalten, wenn der Wert des Eingangssensors im Bereich [untere Grenze ... obere Grenze] liegt.", + "title": "Schwellenwertsensor hinzuf\u00fcgen" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Untere und obere Grenze k\u00f6nnen nicht beide leer sein" + }, + "step": { + "init": { + "data": { + "entity_id": "Eingangssensor", + "hysteresis": "Hysterese", + "lower": "Untere Grenze", + "mode": "Schwellenwertmodus", + "name": "Name", + "upper": "Obergrenze" + }, + "description": "Nur unterer Grenzwert konfiguriert - Einschalten, wenn der Wert des Eingangssensors kleiner als der untere Grenzwert ist.\nNur oberer Grenzwert konfiguriert - Einschalten, wenn der Wert des Eingangssensors gr\u00f6\u00dfer als der obere Grenzwert ist.\nSowohl unterer als auch oberer Grenzwert konfiguriert - Einschalten, wenn der Wert des Eingangssensors im Bereich [unterer Grenzwert ... oberer Grenzwert] liegt." + } + } + }, + "title": "Schwellenwertsensor" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/el.json b/homeassistant/components/threshold/translations/el.json new file mode 100644 index 00000000000..2ad5f25e5f8 --- /dev/null +++ b/homeassistant/components/threshold/translations/el.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "\u03a4\u03b1 \u03ba\u03ac\u03c4\u03c9 \u03ba\u03b1\u03b9 \u03ac\u03bd\u03c9 \u03cc\u03c1\u03b9\u03b1 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03c4\u03b1 \u03b4\u03cd\u03bf \u03ba\u03b5\u03bd\u03ac" + }, + "step": { + "user": { + "data": { + "entity_id": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "hysteresis": "\u03a5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7", + "lower": "\u039a\u03ac\u03c4\u03c9 \u03cc\u03c1\u03b9\u03bf", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bf\u03c1\u03af\u03bf\u03c5", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "upper": "\u0386\u03bd\u03c9 \u03cc\u03c1\u03b9\u03bf" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c0\u03cc\u03c4\u03b5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n\n \u039c\u03cc\u03bd\u03bf \u03c4\u03bf \u03ba\u03b1\u03c4\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03cc\u03c4\u03b1\u03bd \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b9\u03ba\u03c1\u03cc\u03c4\u03b5\u03c1\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03b1\u03c4\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf.\n \u039c\u03cc\u03bd\u03bf \u03c4\u03bf \u03b1\u03bd\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03cc\u03c4\u03b1\u03bd \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03b3\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b1\u03bd\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf.\n \u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b1\u03c4\u03ce\u03c4\u03b5\u03c1\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03ac\u03bd\u03c9 \u03bf\u03c1\u03af\u03bf\u03c5 - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b1\u03bd \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b5\u03cd\u03c1\u03bf\u03c2 [\u03ba\u03ac\u03c4\u03c9 \u03cc\u03c1\u03b9\u03bf .. \u03ac\u03bd\u03c9 \u03cc\u03c1\u03b9\u03bf].", + "title": "\u039d\u03ad\u03bf\u03c2 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03bf\u03c1\u03af\u03bf\u03c5" + } + } + }, + "options": { + "error": { + "need_lower_upper": "\u03a4\u03b1 \u03ba\u03ac\u03c4\u03c9 \u03ba\u03b1\u03b9 \u03ac\u03bd\u03c9 \u03cc\u03c1\u03b9\u03b1 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03c4\u03b1 \u03b4\u03cd\u03bf \u03ba\u03b5\u03bd\u03ac" + }, + "step": { + "init": { + "data": { + "entity_id": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "hysteresis": "\u03a5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7", + "lower": "\u039a\u03ac\u03c4\u03c9 \u03cc\u03c1\u03b9\u03bf", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03bf\u03c1\u03af\u03bf\u03c5", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "upper": "\u0386\u03bd\u03c9 \u03cc\u03c1\u03b9\u03bf" + }, + "description": "\u0394\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03c4\u03b5 \u03c0\u03cc\u03c4\u03b5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2. \n\n \u039c\u03cc\u03bd\u03bf \u03c4\u03bf \u03ba\u03b1\u03c4\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf \u03ad\u03c7\u03b5\u03b9 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03cc\u03c4\u03b1\u03bd \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b9\u03ba\u03c1\u03cc\u03c4\u03b5\u03c1\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03ba\u03b1\u03c4\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf.\n \u039c\u03cc\u03bd\u03bf \u03c4\u03bf \u03b1\u03bd\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03cc\u03c4\u03b1\u03bd \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03b3\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf \u03b1\u03bd\u03ce\u03c4\u03b5\u03c1\u03bf \u03cc\u03c1\u03b9\u03bf.\n \u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ba\u03b1\u03c4\u03ce\u03c4\u03b5\u03c1\u03bf\u03c5 \u03ba\u03b1\u03b9 \u03ac\u03bd\u03c9 \u03bf\u03c1\u03af\u03bf\u03c5 - \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03cc\u03c4\u03b1\u03bd \u03b7 \u03c4\u03b9\u03bc\u03ae \u03c4\u03bf\u03c5 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf \u03b5\u03cd\u03c1\u03bf\u03c2 [\u03ba\u03ac\u03c4\u03c9 \u03cc\u03c1\u03b9\u03bf .. \u03ac\u03bd\u03c9 \u03cc\u03c1\u03b9\u03bf]." + } + } + }, + "title": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03bf\u03c1\u03af\u03bf\u03c5" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/en.json b/homeassistant/components/threshold/translations/en.json index 461ca244353..66a2bb33ddb 100644 --- a/homeassistant/components/threshold/translations/en.json +++ b/homeassistant/components/threshold/translations/en.json @@ -9,6 +9,7 @@ "entity_id": "Input sensor", "hysteresis": "Hysteresis", "lower": "Lower limit", + "mode": "Threshold mode", "name": "Name", "upper": "Upper limit" }, @@ -27,6 +28,7 @@ "entity_id": "Input sensor", "hysteresis": "Hysteresis", "lower": "Lower limit", + "mode": "Threshold mode", "name": "Name", "upper": "Upper limit" }, diff --git a/homeassistant/components/threshold/translations/et.json b/homeassistant/components/threshold/translations/et.json new file mode 100644 index 00000000000..7a41c0bfc3f --- /dev/null +++ b/homeassistant/components/threshold/translations/et.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Alam- ja \u00fclempiir ei saa korraga puududa" + }, + "step": { + "user": { + "data": { + "entity_id": "Sisendandur", + "hysteresis": "H\u00fcsterees", + "lower": "Alampiir", + "mode": "L\u00e4vendi kasutamine", + "name": "Nimi", + "upper": "\u00dclempiir" + }, + "description": "Loo olekuandur mis l\u00fcltub sisse v\u00f5i v\u00e4lja vastavalt m\u00e4\u00e4ratud ajale.\n\nAinult alampiir - l\u00fclitu sisse kui anduri v\u00e4\u00e4rtus on alla almpiiri.\nAinult \u00fclempiir - l\u00fclitu sisse kui anduri v\u00e4\u00e4rtus on \u00fcle \u00fclempiiri.\nAlam- ja \u00fclempiir m\u00f5lemad m\u00e4\u00e4ratud - l\u00fclitu sisse kui anduri v\u00e4\u00e4rtus on vahemikus [alampiir .. \u00fclempiir]", + "title": "Lisa uus l\u00e4vendiandur" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Alam- ja \u00fclempiir ei saa korraga puududa" + }, + "step": { + "init": { + "data": { + "entity_id": "Sisendandur", + "hysteresis": "H\u00fcsterees", + "lower": "Alampiir", + "mode": "L\u00e4vendi kasutamine", + "name": "Nimi", + "upper": "\u00dclempiir" + }, + "description": "Ainult alampiir - l\u00fclitu sisse kui anduri v\u00e4\u00e4rtus on alla almpiiri.\nAinult \u00fclempiir - l\u00fclitu sisse kui anduri v\u00e4\u00e4rtus on \u00fcle \u00fclempiiri.\nAlam- ja \u00fclempiir m\u00f5lemad m\u00e4\u00e4ratud - l\u00fclitu sisse kui anduri v\u00e4\u00e4rtus on vahemikus [alampiir .. \u00fclempiir]" + } + } + }, + "title": "L\u00e4vendiandur" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/fr.json b/homeassistant/components/threshold/translations/fr.json new file mode 100644 index 00000000000..29bc21e9cd5 --- /dev/null +++ b/homeassistant/components/threshold/translations/fr.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Les limites inf\u00e9rieure et sup\u00e9rieure ne peuvent pas \u00eatre toutes les deux vides" + }, + "step": { + "user": { + "data": { + "entity_id": "Capteur d'entr\u00e9e", + "hysteresis": "Hyst\u00e9r\u00e9sis", + "lower": "Limite inf\u00e9rieure", + "mode": "Mode de seuil", + "name": "Nom", + "upper": "Limite sup\u00e9rieure" + }, + "description": "Cr\u00e9ez un capteur binaire qui s'active et se d\u00e9sactive en fonction de la valeur d'un autre capteur.\n\nSi seule la limite inf\u00e9rieure est configur\u00e9e, le capteur est activ\u00e9 lorsque la valeur du capteur d'entr\u00e9e est inf\u00e9rieure \u00e0 cette limite.\nSi seule la limite sup\u00e9rieure est configur\u00e9e, le capteur est activ\u00e9 lorsque la valeur du capteur d'entr\u00e9e est sup\u00e9rieure \u00e0 cette limite.\nSi les deux limites sont configur\u00e9es, le capteur est activ\u00e9 lorsque la valeur du capteur d'entr\u00e9e est comprise entre les limites inf\u00e9rieure et sup\u00e9rieure.", + "title": "Ajouter un capteur de seuil" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Les limites inf\u00e9rieure et sup\u00e9rieure ne peuvent pas \u00eatre toutes les deux vides" + }, + "step": { + "init": { + "data": { + "entity_id": "Capteur d'entr\u00e9e", + "hysteresis": "Hyst\u00e9r\u00e9sis", + "lower": "Limite inf\u00e9rieure", + "mode": "Mode de seuil", + "name": "Nom", + "upper": "Limite sup\u00e9rieure" + }, + "description": "Si seule la limite inf\u00e9rieure est configur\u00e9e, le capteur est activ\u00e9 lorsque la valeur du capteur d'entr\u00e9e est inf\u00e9rieure \u00e0 cette limite.\nSi seule la limite sup\u00e9rieure est configur\u00e9e, le capteur est activ\u00e9 lorsque la valeur du capteur d'entr\u00e9e est sup\u00e9rieure \u00e0 cette limite.\nSi les deux limites sont configur\u00e9es, le capteur est activ\u00e9 lorsque la valeur du capteur d'entr\u00e9e est comprise entre les limites inf\u00e9rieure et sup\u00e9rieure." + } + } + }, + "title": "Capteur de seuil" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/hu.json b/homeassistant/components/threshold/translations/hu.json new file mode 100644 index 00000000000..944a13680d3 --- /dev/null +++ b/homeassistant/components/threshold/translations/hu.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Az als\u00f3 \u00e9s a fels\u0151 hat\u00e1r\u00e9rt\u00e9k nem lehet \u00fcres" + }, + "step": { + "user": { + "data": { + "entity_id": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "hysteresis": "Hiszter\u00e9zis", + "lower": "Als\u00f3 hat\u00e1r", + "mode": "K\u00fcsz\u00f6b\u00e9rt\u00e9k m\u00f3d", + "name": "Elnevez\u00e9s", + "upper": "Fels\u0151 hat\u00e1r" + }, + "description": "Itt \u00e1ll\u00edthatja be, hogy az \u00e9rz\u00e9kel\u0151 mikor kapcsoljon be \u00e9s mikor kapcsoljon ki.\n\nCsak als\u00f3 hat\u00e1r\u00e9rt\u00e9k konfigur\u00e1lva - Bekapcsol\u00e1s, amikor a forr\u00e1s \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke kisebb, mint az als\u00f3 hat\u00e1r\u00e9rt\u00e9k.\nCsak fels\u0151 hat\u00e1r\u00e9rt\u00e9k konfigur\u00e1lva - Bekapcsol, ha a forr\u00e1s \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke nagyobb, mint a fels\u0151 hat\u00e1r\u00e9rt\u00e9k.\nAls\u00f3 \u00e9s fels\u0151 hat\u00e1r\u00e9rt\u00e9k konfigur\u00e1lva - Bekapcsol\u00e1s, ha a forr\u00e1s \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke az als\u00f3 hat\u00e1r\u00e9rt\u00e9k \u00e9s fels\u0151 hat\u00e1r\u00e9rt\u00e9k k\u00f6z\u00f6tti tartom\u00e1nyban van.", + "title": "\u00daj k\u00fcsz\u00f6b\u00e9rt\u00e9k-\u00e9rz\u00e9kel\u0151" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Az als\u00f3 \u00e9s a fels\u0151 hat\u00e1r\u00e9rt\u00e9k nem lehet \u00fcres" + }, + "step": { + "init": { + "data": { + "entity_id": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "hysteresis": "Hiszter\u00e9zis", + "lower": "Als\u00f3 hat\u00e1r", + "mode": "K\u00fcsz\u00f6b\u00e9rt\u00e9k m\u00f3d", + "name": "Elnevez\u00e9s", + "upper": "Fels\u0151 hat\u00e1r" + }, + "description": "Itt \u00e1ll\u00edthatja be, hogy az \u00e9rz\u00e9kel\u0151 mikor kapcsoljon be \u00e9s mikor kapcsoljon ki.\n\nCsak als\u00f3 hat\u00e1r\u00e9rt\u00e9k konfigur\u00e1lva - Bekapcsol\u00e1s, amikor a forr\u00e1s \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke kisebb, mint az als\u00f3 hat\u00e1r\u00e9rt\u00e9k.\nCsak fels\u0151 hat\u00e1r\u00e9rt\u00e9k konfigur\u00e1lva - Bekapcsol, ha a forr\u00e1s \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke nagyobb, mint a fels\u0151 hat\u00e1r\u00e9rt\u00e9k.\nAls\u00f3 \u00e9s fels\u0151 hat\u00e1r\u00e9rt\u00e9k konfigur\u00e1lva - Bekapcsol\u00e1s, ha a forr\u00e1s \u00e9rz\u00e9kel\u0151 \u00e9rt\u00e9ke az als\u00f3 hat\u00e1r\u00e9rt\u00e9k \u00e9s fels\u0151 hat\u00e1r\u00e9rt\u00e9k k\u00f6z\u00f6tti tartom\u00e1nyban van." + } + } + }, + "title": "K\u00fcsz\u00f6b\u00e9rt\u00e9k \u00e9rz\u00e9kel\u0151" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/id.json b/homeassistant/components/threshold/translations/id.json new file mode 100644 index 00000000000..7356dd772e1 --- /dev/null +++ b/homeassistant/components/threshold/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Batas bawah dan batas atas tidak boleh kosong sekaligus" + }, + "step": { + "user": { + "data": { + "entity_id": "Sensor input", + "hysteresis": "Histeresis", + "lower": "Batas bawah", + "mode": "Mode ambang batas", + "name": "Nama", + "upper": "Batas atas" + }, + "description": "Buat sensor biner yang nyala dan mati tergantung pada nilai sensor \n\nHanya batas bawah yang dikonfigurasi: Nyalakan saat nilai sensor input kurang dari batas bawah.\nHanya batas atas yang dikonfigurasi: Nyalakan saat nilai sensor input lebih besar dari batas atas.\nKedua batas dikonfigurasi: Nyalakan ketika nilai sensor input berada dalam rentang [batas bawah ... batas atas].", + "title": "Tambahkan Sensor Ambang Batas" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Batas bawah dan batas atas tidak boleh kosong sekaligus" + }, + "step": { + "init": { + "data": { + "entity_id": "Sensor input", + "hysteresis": "Histeresis", + "lower": "Batas bawah", + "mode": "Mode ambang batas", + "name": "Nama", + "upper": "Batas atas" + }, + "description": "Hanya batas bawah yang dikonfigurasi: Nyalakan saat nilai sensor input kurang dari batas bawah.\nHanya batas atas yang dikonfigurasi: Nyalakan saat nilai sensor input lebih besar dari batas atas.\nKedua batas dikonfigurasi: Nyalakan ketika nilai sensor input berada dalam rentang [batas bawah ... batas atas]." + } + } + }, + "title": "Sensor Ambang Batas" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/it.json b/homeassistant/components/threshold/translations/it.json new file mode 100644 index 00000000000..6bc62dce765 --- /dev/null +++ b/homeassistant/components/threshold/translations/it.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "I limiti inferiore e superiore non possono essere entrambi vuoti" + }, + "step": { + "user": { + "data": { + "entity_id": "Sensore di ingresso", + "hysteresis": "Isteresi", + "lower": "Limite inferiore", + "mode": "Modalit\u00e0 soglia", + "name": "Nome", + "upper": "Limite superiore" + }, + "description": "Crea un sensore binario che si accende e si spegne a seconda del valore di un sensore \n\nSolo limite inferiore configurato - si accende quando il valore del sensore di ingresso \u00e8 inferiore al limite inferiore.\nSolo limite superiore configurato - si accende quando il valore del sensore di ingresso \u00e8 maggiore del limite superiore.\nSia il limite inferiore che quello superiore configurati - si accende quando il valore del sensore di ingresso \u00e8 compreso nell'intervallo [limite inferiore .. limite superiore].", + "title": "Aggiungi sensore di soglia" + } + } + }, + "options": { + "error": { + "need_lower_upper": "I limiti inferiore e superiore non possono essere entrambi vuoti" + }, + "step": { + "init": { + "data": { + "entity_id": "Sensore di ingresso", + "hysteresis": "Isteresi", + "lower": "Limite inferiore", + "mode": "Modalit\u00e0 soglia", + "name": "Nome", + "upper": "Limite superiore" + }, + "description": "Solo limite inferiore configurato - si accende quando il valore del sensore di ingresso \u00e8 inferiore al limite inferiore.\nSolo limite superiore configurato - si accende quando il valore del sensore di ingresso \u00e8 maggiore del limite superiore.\nSia il limite inferiore che quello superiore configurati - si accende quando il valore del sensore di ingresso \u00e8 compreso nell'intervallo [limite inferiore .. limite superiore]." + } + } + }, + "title": "Sensore di soglia" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/ja.json b/homeassistant/components/threshold/translations/ja.json new file mode 100644 index 00000000000..7d5a6cc2ce7 --- /dev/null +++ b/homeassistant/components/threshold/translations/ja.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "\u4e0b\u9650\u3068\u4e0a\u9650\u306e\u4e21\u65b9\u3092\u7a7a\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" + }, + "step": { + "user": { + "data": { + "entity_id": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "hysteresis": "\u30d2\u30b9\u30c6\u30ea\u30b7\u30b9", + "lower": "\u4e0b\u9650\u5024", + "mode": "\u3057\u304d\u3044\u5024\u30e2\u30fc\u30c9", + "name": "\u540d\u524d", + "upper": "\u4e0a\u9650\u5024" + }, + "description": "\u30bb\u30f3\u30b5\u30fc\u306e\u30aa\u30f3\u3068\u30aa\u30d5\u3092\u5207\u308a\u66ff\u3048\u308b\u30bf\u30a4\u30df\u30f3\u30b0\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002\n\n\u4e0b\u9650\u306e\u307f\u8a2d\u5b9a - \u5165\u529b\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u304c\u4e0b\u9650\u5024\u3088\u308a\u5c0f\u3055\u3044\u3068\u304d\u306b\u30aa\u30f3\u306b\u3057\u307e\u3059\u3002\n\u4e0a\u9650\u306e\u307f\u8a2d\u5b9a - \u5165\u529b\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u304c\u4e0a\u9650\u5024\u3088\u308a\u5927\u304d\u3044\u3068\u304d\u306b\u30aa\u30f3\u306b\u3057\u307e\u3059\u3002\n\u4e0b\u9650\u3068\u4e0a\u9650\u306e\u4e21\u65b9\u3092\u8a2d\u5b9a - \u5165\u529b\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u304c[\u4e0b\u9650..\u4e0a\u9650]\u306e\u7bc4\u56f2\u306b\u3042\u308b\u3068\u304d\u306b\u30aa\u30f3\u306b\u3057\u307e\u3059\u3002", + "title": "\u65b0\u3057\u3044\u3057\u304d\u3044\u5024\u30bb\u30f3\u30b5\u30fc" + } + } + }, + "options": { + "error": { + "need_lower_upper": "\u4e0b\u9650\u3068\u4e0a\u9650\u306e\u4e21\u65b9\u3092\u7a7a\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" + }, + "step": { + "init": { + "data": { + "entity_id": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "hysteresis": "\u30d2\u30b9\u30c6\u30ea\u30b7\u30b9", + "lower": "\u4e0b\u9650\u5024", + "mode": "\u3057\u304d\u3044\u5024\u30e2\u30fc\u30c9", + "name": "\u540d\u524d", + "upper": "\u4e0a\u9650\u5024" + }, + "description": "\u30bb\u30f3\u30b5\u30fc\u306e\u30aa\u30f3\u3068\u30aa\u30d5\u3092\u5207\u308a\u66ff\u3048\u308b\u30bf\u30a4\u30df\u30f3\u30b0\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002\n\n\u4e0b\u9650\u306e\u307f\u8a2d\u5b9a - \u5165\u529b\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u304c\u4e0b\u9650\u5024\u3088\u308a\u5c0f\u3055\u3044\u3068\u304d\u306b\u30aa\u30f3\u306b\u3057\u307e\u3059\u3002\n\u4e0a\u9650\u306e\u307f\u8a2d\u5b9a - \u5165\u529b\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u304c\u4e0a\u9650\u5024\u3088\u308a\u5927\u304d\u3044\u3068\u304d\u306b\u30aa\u30f3\u306b\u3057\u307e\u3059\u3002\n\u4e0b\u9650\u3068\u4e0a\u9650\u306e\u4e21\u65b9\u3092\u8a2d\u5b9a - \u5165\u529b\u30bb\u30f3\u30b5\u30fc\u306e\u5024\u304c[\u4e0b\u9650..\u4e0a\u9650]\u306e\u7bc4\u56f2\u306b\u3042\u308b\u3068\u304d\u306b\u30aa\u30f3\u306b\u3057\u307e\u3059\u3002" + } + } + }, + "title": "\u3057\u304d\u3044\u5024\u30bb\u30f3\u30b5\u30fc" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/nl.json b/homeassistant/components/threshold/translations/nl.json new file mode 100644 index 00000000000..73af8fcc8b7 --- /dev/null +++ b/homeassistant/components/threshold/translations/nl.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Onder- en bovengrens kunnen niet beide leeg zijn" + }, + "step": { + "user": { + "data": { + "entity_id": "Invoer sensor", + "hysteresis": "Hyseterise", + "lower": "Ondergrens", + "mode": "Drempelmodus", + "name": "Naam", + "upper": "Bovengrens" + }, + "description": "Maak een binaire sensor die aan en uit gaat afhankelijk van de waarde van een sensor\n\nAlleen ondergrens geconfigureerd - Schakelt in wanneer de waarde van de ingangssensor kleiner is dan de ondergrens.\nAlleen bovengrens geconfigureerd - Schakelt in wanneer de waarde van de ingangssensor groter is dan de bovengrens.\nZowel ondergrens als bovengrens ingesteld - Schakelt in wanneer de waarde van de ingangssensor binnen het bereik [ondergrens ... bovengrens] ligt.", + "title": "Drempelsensor toevoegen" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Onder- en bovengrens kunnen niet beide leeg zijn" + }, + "step": { + "init": { + "data": { + "entity_id": "Invoer sensor", + "hysteresis": "Hyseterise", + "lower": "Ondergrens", + "mode": "Drempelmodus", + "name": "Naam", + "upper": "Bovengrens" + }, + "description": "Alleen ondergrens geconfigureerd - Schakelt in wanneer de waarde van de ingangssensor lager is dan de ondergrens.\nAlleen bovengrens geconfigureerd - Schakelt in wanneer de waarde van de ingangssensor groter is dan de bovengrens.\nZowel ondergrens als bovengrens ingesteld - Schakelt in wanneer de waarde van de ingangssensor binnen het bereik [ondergrens ... bovengrens] ligt." + } + } + }, + "title": "Drempelsensor" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/no.json b/homeassistant/components/threshold/translations/no.json new file mode 100644 index 00000000000..66c8461d8cc --- /dev/null +++ b/homeassistant/components/threshold/translations/no.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Nedre og \u00f8vre grense kan ikke begge v\u00e6re tomme" + }, + "step": { + "user": { + "data": { + "entity_id": "Inngangssensor", + "hysteresis": "Hysterese", + "lower": "Nedre grense", + "mode": "Terskelverdi-modus", + "name": "Navn", + "upper": "\u00d8vre grense" + }, + "description": "Lag en bin\u00e6r sensor som sl\u00e5s av og p\u00e5 avhengig av verdien til en sensor \n\n Kun nedre grense konfigurert - Sl\u00e5 p\u00e5 n\u00e5r inngangssensorens verdi er mindre enn den nedre grensen.\n Bare \u00f8vre grense konfigurert - Sl\u00e5 p\u00e5 n\u00e5r inngangssensorens verdi er st\u00f8rre enn den \u00f8vre grensen.\n B\u00e5de nedre og \u00f8vre grense konfigurert - Sl\u00e5 p\u00e5 n\u00e5r inngangssensorens verdi er innenfor omr\u00e5det [nedre grense .. \u00f8vre grense].", + "title": "Legg til terskelsensor" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Nedre og \u00f8vre grense kan ikke begge v\u00e6re tomme" + }, + "step": { + "init": { + "data": { + "entity_id": "Inngangssensor", + "hysteresis": "Hysterese", + "lower": "Nedre grense", + "mode": "Terskelverdi-modus", + "name": "Navn", + "upper": "\u00d8vre grense" + }, + "description": "Kun nedre grense konfigurert - Sl\u00e5 p\u00e5 n\u00e5r inngangssensorens verdi er mindre enn den nedre grensen.\n Bare \u00f8vre grense konfigurert - Sl\u00e5 p\u00e5 n\u00e5r inngangssensorens verdi er st\u00f8rre enn den \u00f8vre grensen.\n B\u00e5de nedre og \u00f8vre grense konfigurert - Sl\u00e5 p\u00e5 n\u00e5r inngangssensorens verdi er innenfor omr\u00e5det [nedre grense .. \u00f8vre grense]." + } + } + }, + "title": "Terskelsensor" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/pl.json b/homeassistant/components/threshold/translations/pl.json new file mode 100644 index 00000000000..5db231947c2 --- /dev/null +++ b/homeassistant/components/threshold/translations/pl.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Dolny i g\u00f3rny limit nie mog\u0105 by\u0107 jednocze\u015bnie puste" + }, + "step": { + "user": { + "data": { + "entity_id": "Sensor wej\u015bciowy", + "hysteresis": "Op\u00f3\u017anienie", + "lower": "Dolny limit", + "mode": "Tryb progowy", + "name": "Nazwa", + "upper": "G\u00f3rny limit" + }, + "description": "Utw\u00f3rz sensor binarny, kt\u00f3ry w\u0142\u0105cza si\u0119 lub wy\u0142\u0105cza w zale\u017cno\u015bci od warto\u015bci sensora.\n\nSkonfigurowano tylko dolny limit - W\u0142\u0105cza si\u0119, gdy warto\u015b\u0107 sensora wej\u015bciowego jest mniejsza ni\u017c dolny limit.\nSkonfigurowano tylko g\u00f3rny limit - W\u0142\u0105cza si\u0119, gdy warto\u015b\u0107 sensora wej\u015bciowego jest wi\u0119ksza ni\u017c g\u00f3rny limit.\nSkonfigurowano dolny i g\u00f3rny limit - W\u0142\u0105cza si\u0119, gdy warto\u015b\u0107 czujnika wej\u015bciowego znajduje si\u0119 w zakresie [dolny limit .. g\u00f3rny limit].", + "title": "Dodaj sensor progowy" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Dolny i g\u00f3rny limit nie mog\u0105 by\u0107 jednocze\u015bnie puste" + }, + "step": { + "init": { + "data": { + "entity_id": "Sensor wej\u015bciowy", + "hysteresis": "Op\u00f3\u017anienie", + "lower": "Dolny limit", + "mode": "Tryb progowy", + "name": "Nazwa", + "upper": "G\u00f3rny limit" + }, + "description": "Skonfigurowano tylko dolny limit - W\u0142\u0105cza si\u0119, gdy warto\u015b\u0107 sensora wej\u015bciowego jest mniejsza ni\u017c dolny limit.\nSkonfigurowano tylko g\u00f3rny limit - W\u0142\u0105cza, gdy warto\u015b\u0107 sensora wej\u015bciowego jest wi\u0119ksza ni\u017c g\u00f3rny limit.\nSkonfigurowano zar\u00f3wno dolny, jak i g\u00f3rny limit - W\u0142\u0105cza, gdy warto\u015b\u0107 sensora wej\u015bciowego znajduje si\u0119 w zakresie [dolny limit .. g\u00f3rny limit]." + } + } + }, + "title": "Sensor progowy" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/pt-BR.json b/homeassistant/components/threshold/translations/pt-BR.json new file mode 100644 index 00000000000..1aa7358086a --- /dev/null +++ b/homeassistant/components/threshold/translations/pt-BR.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Os limites inferior e superior n\u00e3o podem estar vazios" + }, + "step": { + "user": { + "data": { + "entity_id": "Sensor de entrada", + "hysteresis": "Histerese", + "lower": "Limite inferior", + "mode": "Modo Threshold", + "name": "Nome", + "upper": "Limite superior" + }, + "description": "Crie um sensor bin\u00e1rio que liga e desliga dependendo do valor de um sensor \n\n Somente limite inferior configurado - Liga quando o valor do sensor de entrada for menor que o limite inferior.\n Somente limite superior configurado - Liga quando o valor do sensor de entrada for maior que o limite superior.\n Ambos os limites inferior e superior configurados - Liga quando o valor do sensor de entrada est\u00e1 na faixa [limite inferior .. limite superior].", + "title": "Adicionar sensor Threshold" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Os limites inferior e superior n\u00e3o podem estar vazios" + }, + "step": { + "init": { + "data": { + "entity_id": "Sensor de entrada", + "hysteresis": "Histerese", + "lower": "Limite inferior", + "mode": "Modo Threshold", + "name": "Nome", + "upper": "Limite superior" + }, + "description": "Somente o limite inferior \u00e9 configurado - Liga quando o valor do sensor de entrada for menor que o limite inferior.\nSomente o limite superior \u00e9 configurado - Liga quando o valor do sensor de entrada for maior que o limite superior.\nAmbos os limites inferior e superior s\u00e3o configurados - Liga quando o valor do sensor de entrada est\u00e1 na faixa [limite inferior .. limite superior]." + } + } + }, + "title": "Sensor Threshold" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/ru.json b/homeassistant/components/threshold/translations/ru.json new file mode 100644 index 00000000000..5b8c4546823 --- /dev/null +++ b/homeassistant/components/threshold/translations/ru.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "\u041d\u0438\u0436\u043d\u0438\u0439 \u0438 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b\u044b \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0443\u0441\u0442\u044b\u043c\u0438." + }, + "step": { + "user": { + "data": { + "entity_id": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "hysteresis": "\u0413\u0438\u0441\u0442\u0435\u0440\u0435\u0437\u0438\u0441", + "lower": "\u041d\u0438\u0436\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b", + "mode": "\u041f\u043e\u0440\u043e\u0433\u043e\u0432\u044b\u0439 \u0440\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "upper": "\u0412\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b" + }, + "description": "\u0411\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430. \n\n\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0438\u0436\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2014 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u043c\u0435\u043d\u044c\u0448\u0435 \u043d\u0438\u0436\u043d\u0435\u0433\u043e \u043f\u0440\u0435\u0434\u0435\u043b\u0430.\n\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2014 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0435\u0440\u0445\u043d\u0435\u0433\u043e \u043f\u0440\u0435\u0434\u0435\u043b\u0430.\n\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u043a\u0430\u043a \u043d\u0438\u0436\u043d\u0438\u0439, \u0442\u0430\u043a \u0438 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2014 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 [\u043d\u0438\u0436\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2026 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b].", + "title": "\u041f\u043e\u0440\u043e\u0433\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435" + } + } + }, + "options": { + "error": { + "need_lower_upper": "\u041d\u0438\u0436\u043d\u0438\u0439 \u0438 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b\u044b \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0443\u0441\u0442\u044b\u043c\u0438." + }, + "step": { + "init": { + "data": { + "entity_id": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "hysteresis": "\u0413\u0438\u0441\u0442\u0435\u0440\u0435\u0437\u0438\u0441", + "lower": "\u041d\u0438\u0436\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b", + "mode": "\u041f\u043e\u0440\u043e\u0433\u043e\u0432\u044b\u0439 \u0440\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "upper": "\u0412\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0438\u0436\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2014 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u043c\u0435\u043d\u044c\u0448\u0435 \u043d\u0438\u0436\u043d\u0435\u0433\u043e \u043f\u0440\u0435\u0434\u0435\u043b\u0430.\n\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2014 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0435\u0440\u0445\u043d\u0435\u0433\u043e \u043f\u0440\u0435\u0434\u0435\u043b\u0430.\n\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u0438 \u043d\u0438\u0436\u043d\u0438\u0439 \u0438 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b\u044b \u2014 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0435 [\u043d\u0438\u0436\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b \u2026 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0435\u0434\u0435\u043b]." + } + } + }, + "title": "\u041f\u043e\u0440\u043e\u0433\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/tr.json b/homeassistant/components/threshold/translations/tr.json new file mode 100644 index 00000000000..0cb338ef681 --- /dev/null +++ b/homeassistant/components/threshold/translations/tr.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "Alt ve \u00fcst limitlerin ikisi de bo\u015f olamaz" + }, + "step": { + "user": { + "data": { + "entity_id": "Giri\u015f sens\u00f6r\u00fc", + "hysteresis": "Histeresis", + "lower": "Alt s\u0131n\u0131r", + "mode": "E\u015fik modu", + "name": "Ad", + "upper": "\u00dcst s\u0131n\u0131r" + }, + "description": "Bir sens\u00f6r\u00fcn de\u011ferine ba\u011fl\u0131 olarak a\u00e7\u0131l\u0131p kapanan bir ikili sens\u00f6r olu\u015fturun \n\n Yaln\u0131zca alt limit konfig\u00fcre edildi - Giri\u015f sens\u00f6r\u00fcn\u00fcn de\u011feri alt limitten d\u00fc\u015f\u00fck oldu\u011funda a\u00e7\u0131n.\n Yaln\u0131zca \u00fcst limit konfig\u00fcre edildi - Giri\u015f sens\u00f6r\u00fcn\u00fcn de\u011feri \u00fcst limitten b\u00fcy\u00fck oldu\u011funda a\u00e7\u0131n.\n Hem alt hem de \u00fcst limit konfig\u00fcre edildi - Giri\u015f sens\u00f6r\u00fcn\u00fcn de\u011feri [alt limit .. \u00fcst limit] aral\u0131\u011f\u0131nda oldu\u011funda a\u00e7\u0131n.", + "title": "E\u015fik Sens\u00f6r\u00fc Ekle" + } + } + }, + "options": { + "error": { + "need_lower_upper": "Alt ve \u00fcst limitlerin ikisi de bo\u015f olamaz" + }, + "step": { + "init": { + "data": { + "entity_id": "Giri\u015f sens\u00f6r\u00fc", + "hysteresis": "Histeresis", + "lower": "Alt s\u0131n\u0131r", + "mode": "E\u015fik modu", + "name": "Ad", + "upper": "\u00dcst s\u0131n\u0131r" + }, + "description": "Yaln\u0131zca alt limit konfig\u00fcre edildi - Giri\u015f sens\u00f6r\u00fcn\u00fcn de\u011feri alt limitten d\u00fc\u015f\u00fck oldu\u011funda a\u00e7\u0131n.\n Yaln\u0131zca \u00fcst limit konfig\u00fcre edildi - Giri\u015f sens\u00f6r\u00fcn\u00fcn de\u011feri \u00fcst limitten b\u00fcy\u00fck oldu\u011funda a\u00e7\u0131n.\n Hem alt hem de \u00fcst limit konfig\u00fcre edildi - Giri\u015f sens\u00f6r\u00fcn\u00fcn de\u011feri [alt limit .. \u00fcst limit] aral\u0131\u011f\u0131nda oldu\u011funda a\u00e7\u0131n." + } + } + }, + "title": "E\u015fik Sens\u00f6r\u00fc" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/zh-Hans.json b/homeassistant/components/threshold/translations/zh-Hans.json new file mode 100644 index 00000000000..35bc919f0d1 --- /dev/null +++ b/homeassistant/components/threshold/translations/zh-Hans.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "\u4e0b\u9650\u548c\u4e0a\u9650\u4e0d\u80fd\u540c\u65f6\u4e3a\u7a7a" + }, + "step": { + "user": { + "data": { + "entity_id": "\u8f93\u5165\u4f20\u611f\u5668", + "hysteresis": "\u9632\u6296\u8303\u56f4", + "lower": "\u4e0b\u9650", + "mode": "\u9608\u503c\u6a21\u5f0f", + "name": "\u540d\u79f0", + "upper": "\u4e0a\u9650" + }, + "description": "\u521b\u5efa\u6839\u636e\u5176\u4ed6\u4f20\u611f\u5668\u7684\u503c\u6539\u53d8\u5f00\u5173\u72b6\u6001\u7684\u4e8c\u5143\u4f20\u611f\u5668\u3002\n\n\u5982\u679c\u53ea\u914d\u7f6e\u4e0b\u9650\uff0c\u5219\u5f53\u8f93\u5165\u4f20\u611f\u5668\u7684\u503c\u4f4e\u4e8e\u4e0b\u9650\u65f6\u4e3a\u5f00\u3002\n\u5982\u679c\u53ea\u914d\u7f6e\u4e0a\u9650\uff0c\u5219\u5f53\u8f93\u5165\u4f20\u611f\u5668\u7684\u503c\u9ad8\u4e8e\u4e0a\u7ebf\u65f6\u4e3a\u5f00\u3002\n\u5982\u679c\u540c\u65f6\u914d\u7f6e\u4e0a\u9650\u548c\u4e0b\u9650\uff0c\u5219\u5f53\u8f93\u5165\u4f20\u611f\u5668\u7684\u503c\u4ecb\u4e8e\u4e0a\u9650\u548c\u4e0b\u9650\u4e4b\u95f4\uff08\u542b\u4e0a\u9650\u548c\u4e0b\u9650\uff09\u65f6\u4e3a\u5f00\u3002", + "title": "\u6dfb\u52a0\u9608\u503c\u4f20\u611f\u5668" + } + } + }, + "options": { + "error": { + "need_lower_upper": "\u4e0b\u9650\u548c\u4e0a\u9650\u4e0d\u80fd\u540c\u65f6\u4e3a\u7a7a" + }, + "step": { + "init": { + "data": { + "entity_id": "\u8f93\u5165\u4f20\u611f\u5668", + "hysteresis": "\u9632\u6296\u8303\u56f4", + "lower": "\u4e0b\u9650", + "mode": "\u9608\u503c\u6a21\u5f0f", + "name": "\u540d\u79f0", + "upper": "\u4e0a\u9650" + }, + "description": "\u5982\u679c\u53ea\u914d\u7f6e\u4e0b\u9650\uff0c\u5219\u5f53\u8f93\u5165\u4f20\u611f\u5668\u7684\u503c\u4f4e\u4e8e\u4e0b\u9650\u65f6\u4e3a\u5f00\u3002\n\u5982\u679c\u53ea\u914d\u7f6e\u4e0a\u9650\uff0c\u5219\u5f53\u8f93\u5165\u4f20\u611f\u5668\u7684\u503c\u9ad8\u4e8e\u4e0a\u9650\u65f6\u4e3a\u5f00\u3002\n\u5982\u679c\u540c\u65f6\u914d\u7f6e\u4e0a\u9650\u548c\u4e0b\u9650\uff0c\u5219\u5f53\u8f93\u5165\u4f20\u611f\u5668\u7684\u503c\u4ecb\u4e8e\u4e0a\u9650\u548c\u4e0b\u9650\u4e4b\u95f4\uff08\u542b\u4e0a\u9650\u548c\u4e0b\u9650\uff09\u65f6\u4e3a\u5f00\u3002" + } + } + }, + "title": "\u9608\u503c\u4f20\u611f\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/threshold/translations/zh-Hant.json b/homeassistant/components/threshold/translations/zh-Hant.json new file mode 100644 index 00000000000..66bdfe6915a --- /dev/null +++ b/homeassistant/components/threshold/translations/zh-Hant.json @@ -0,0 +1,40 @@ +{ + "config": { + "error": { + "need_lower_upper": "\u4e0a\u4e0b\u9650\u4e0d\u53ef\u540c\u6642\u70ba\u7a7a\u767d" + }, + "step": { + "user": { + "data": { + "entity_id": "\u8f38\u5165\u611f\u6e2c\u5668", + "hysteresis": "\u9072\u6eef", + "lower": "\u4e0b\u9650", + "mode": "\u81e8\u754c\u9ede\u5f0f", + "name": "\u540d\u7a31", + "upper": "\u4e0a\u9650" + }, + "description": "\u65b0\u589e\u6839\u64da\u6578\u503c\u6c7a\u5b9a\u958b\u95dc\u4e4b\u4e8c\u9032\u4f4d\u611f\u6e2c\u5668\u3002\n\n\u50c5\u8a2d\u5b9a\u4e0b\u9650 - \u7576\u8f38\u5165\u611f\u6e2c\u5668\u6578\u503c\u4f4e\u65bc\u4e0b\u9650\u6642\u3001\u958b\u555f\u3002\n\u50c5\u8a2d\u5b9a\u4e0a\u9650 - \u7576\u8f38\u5165\u611f\u6e2c\u5668\u6578\u503c\u9ad8\u65bc\u4e0a\u9650\u6642\u3001\u958b\u555f\u3002\n\u540c\u6642\u8a2d\u5b9a\u4e0a\u4e0b\u9650 - \u7576\u8f38\u5165\u611f\u6e2c\u5668\u6578\u503c\u65bc\u7bc4\u570d\u5167 [\u4e0b\u9650 .. \u4e0a\u9650] \u6642\u958b\u555f\u3002", + "title": "\u65b0\u589e\u81e8\u754c\u611f\u6e2c\u5668" + } + } + }, + "options": { + "error": { + "need_lower_upper": "\u4e0a\u4e0b\u9650\u4e0d\u53ef\u540c\u6642\u70ba\u7a7a\u767d" + }, + "step": { + "init": { + "data": { + "entity_id": "\u8f38\u5165\u611f\u6e2c\u5668", + "hysteresis": "\u9072\u6eef", + "lower": "\u4e0b\u9650", + "mode": "\u81e8\u754c\u9ede\u5f0f", + "name": "\u540d\u7a31", + "upper": "\u4e0a\u9650" + }, + "description": "\u50c5\u8a2d\u5b9a\u4e0b\u9650 - \u7576\u8f38\u5165\u611f\u6e2c\u5668\u6578\u503c\u4f4e\u65bc\u4e0b\u9650\u6642\u3001\u958b\u555f\u3002\n\u50c5\u8a2d\u5b9a\u4e0a\u9650 - \u7576\u8f38\u5165\u611f\u6e2c\u5668\u6578\u503c\u9ad8\u65bc\u4e0a\u9650\u6642\u3001\u958b\u555f\u3002\n\u540c\u6642\u8a2d\u5b9a\u4e0a\u4e0b\u9650 - \u7576\u8f38\u5165\u611f\u6e2c\u5668\u6578\u503c\u65bc\u7bc4\u570d\u5167 [\u4e0b\u9650 .. \u4e0a\u9650] \u6642\u958b\u555f\u3002" + } + } + }, + "title": "\u81e8\u754c\u611f\u6e2c\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 598810b1a0a..b07cebde680 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -3,7 +3,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.22.2"], + "requirements": ["pyTibber==0.22.3"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, diff --git a/homeassistant/components/tikteck/light.py b/homeassistant/components/tikteck/light.py index 08809165bf3..0f810c434fb 100644 --- a/homeassistant/components/tikteck/light.py +++ b/homeassistant/components/tikteck/light.py @@ -10,8 +10,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_PASSWORD @@ -23,8 +22,6 @@ import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -SUPPORT_TIKTECK_LED = SUPPORT_BRIGHTNESS | SUPPORT_COLOR - DEVICE_SCHEMA = vol.Schema( {vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} ) @@ -57,6 +54,9 @@ def setup_platform( class TikteckLight(LightEntity): """Representation of a Tikteck light.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, device): """Initialize the light.""" @@ -97,11 +97,6 @@ class TikteckLight(LightEntity): """Return the color property.""" return self._hs - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_TIKTECK_LED - @property def should_poll(self): """Return the polling state.""" diff --git a/homeassistant/components/tod/config_flow.py b/homeassistant/components/tod/config_flow.py index bd1712d1db5..5155d15561b 100644 --- a/homeassistant/components/tod/config_flow.py +++ b/homeassistant/components/tod/config_flow.py @@ -18,14 +18,14 @@ from .const import CONF_AFTER_TIME, CONF_BEFORE_TIME, DOMAIN OPTIONS_SCHEMA = vol.Schema( { - vol.Required(CONF_AFTER_TIME): selector.selector({"time": {}}), - vol.Required(CONF_BEFORE_TIME): selector.selector({"time": {}}), + vol.Required(CONF_AFTER_TIME): selector.TimeSelector(), + vol.Required(CONF_BEFORE_TIME): selector.TimeSelector(), } ) CONFIG_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): selector.selector({"text": {}}), + vol.Required(CONF_NAME): selector.TextSelector(), } ).extend(OPTIONS_SCHEMA.schema) diff --git a/homeassistant/components/tod/translations/bg.json b/homeassistant/components/tod/translations/bg.json new file mode 100644 index 00000000000..35cfa0ad1d7 --- /dev/null +++ b/homeassistant/components/tod/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/ca.json b/homeassistant/components/tod/translations/ca.json new file mode 100644 index 00000000000..3908cb7761f --- /dev/null +++ b/homeassistant/components/tod/translations/ca.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Activa despr\u00e9s de", + "after_time": "Temps d'activaci\u00f3", + "before": "Desactiva despr\u00e9s de", + "before_time": "Temps de desactivaci\u00f3", + "name": "Nom" + }, + "description": "Crea un sensor binari que s'activa o es desactiva en funci\u00f3 de l'hora.", + "title": "Afegeix sensor temps del dia" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Activa despr\u00e9s de", + "after_time": "Temps d'activaci\u00f3", + "before": "Desactiva despr\u00e9s de", + "before_time": "Temps de desactivaci\u00f3" + }, + "description": "Crea un sensor binari que s'activa o es desactiva en funci\u00f3 de l'hora." + } + } + }, + "title": "Sensor temps del dia" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/de.json b/homeassistant/components/tod/translations/de.json new file mode 100644 index 00000000000..eeb3d4dd8f9 --- /dev/null +++ b/homeassistant/components/tod/translations/de.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Angeschaltet nach", + "after_time": "Einschaltzeit", + "before": "Ausgeschaltet nach", + "before_time": "Ausschaltzeit", + "name": "Name" + }, + "description": "Erstelle einen bin\u00e4ren Sensor, der sich je nach Uhrzeit ein- oder ausschaltet.", + "title": "Tageszeitsensor hinzuf\u00fcgen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Angeschaltet nach", + "after_time": "Einschaltzeit", + "before": "Ausgeschaltet nach", + "before_time": "Ausschaltzeit" + }, + "description": "Erstelle einen bin\u00e4ren Sensor, der sich je nach Uhrzeit ein- oder ausschaltet." + } + } + }, + "title": "Tageszeitensensor" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/el.json b/homeassistant/components/tod/translations/el.json new file mode 100644 index 00000000000..ffa426365a4 --- /dev/null +++ b/homeassistant/components/tod/translations/el.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b5\u03c4\u03ac", + "after_time": "\u0395\u03bd\u03c4\u03cc\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5", + "before": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b5\u03c4\u03ac", + "before_time": "\u0395\u03ba\u03c4\u03cc\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c0\u03cc\u03c4\u03b5 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9.", + "title": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 New Times of the Day" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b5\u03c4\u03ac", + "after_time": "\u0395\u03bd\u03c4\u03cc\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5", + "before": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03bc\u03b5\u03c4\u03ac", + "before_time": "\u0395\u03ba\u03c4\u03cc\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5" + }, + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c0\u03cc\u03c4\u03b5 \u03bf \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b8\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9." + } + } + }, + "title": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 Times of the Day" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/en.json b/homeassistant/components/tod/translations/en.json index 2ecb2c695c8..ced14151519 100644 --- a/homeassistant/components/tod/translations/en.json +++ b/homeassistant/components/tod/translations/en.json @@ -3,7 +3,9 @@ "step": { "user": { "data": { + "after": "On after", "after_time": "On time", + "before": "Off after", "before_time": "Off time", "name": "Name" }, @@ -16,9 +18,12 @@ "step": { "init": { "data": { + "after": "On after", "after_time": "On time", + "before": "Off after", "before_time": "Off time" - } + }, + "description": "Create a binary sensor that turns on or off depending on the time." } } }, diff --git a/homeassistant/components/tod/translations/et.json b/homeassistant/components/tod/translations/et.json new file mode 100644 index 00000000000..06b40e0ce72 --- /dev/null +++ b/homeassistant/components/tod/translations/et.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Kestus sissel\u00fclitumisest", + "after_time": "Seesoleku aeg", + "before": "Kestus v\u00e4ljal\u00fclitumisest", + "before_time": "V\u00e4ljasoleku aeg", + "name": "Nimi" + }, + "description": "Loo olekuandur mis l\u00fcltub sisse v\u00f5i v\u00e4lja vastavalt m\u00e4\u00e4ratud ajale.", + "title": "Lisa \u00f6\u00f6p\u00e4eva andur" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Kestus sissel\u00fclitumisest", + "after_time": "Seesoleku aeg", + "before": "Kestus v\u00e4ljal\u00fclitumisest", + "before_time": "V\u00e4ljasoleku aeg" + }, + "description": "Vali millal andur on sisse v\u00f5i v\u00e4lja l\u00fclitatud." + } + } + }, + "title": "\u00d6\u00f6p\u00e4eva andur" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/fr.json b/homeassistant/components/tod/translations/fr.json new file mode 100644 index 00000000000..799e286343b --- /dev/null +++ b/homeassistant/components/tod/translations/fr.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Activ\u00e9 apr\u00e8s", + "after_time": "Heure d'activation", + "before": "D\u00e9sactiv\u00e9 apr\u00e8s", + "before_time": "Heure de d\u00e9sactivation", + "name": "Nom" + }, + "description": "Cr\u00e9ez un capteur binaire qui s'active et se d\u00e9sactive en fonction de l'heure.", + "title": "Ajouter un capteur de moment de la journ\u00e9e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Activ\u00e9 apr\u00e8s", + "after_time": "Heure d'activation", + "before": "D\u00e9sactiv\u00e9 apr\u00e8s", + "before_time": "Heure de d\u00e9sactivation" + }, + "description": "Cr\u00e9ez un capteur binaire qui s'active et se d\u00e9sactive en fonction de l'heure." + } + } + }, + "title": "Capteur de moment de la journ\u00e9e" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/he.json b/homeassistant/components/tod/translations/he.json new file mode 100644 index 00000000000..b10f9e2b1ca --- /dev/null +++ b/homeassistant/components/tod/translations/he.json @@ -0,0 +1,30 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "\u05d4\u05e4\u05e2\u05dc\u05d4 \u05dc\u05d0\u05d7\u05e8", + "after_time": "\u05d6\u05de\u05df \u05d4\u05e4\u05e2\u05dc\u05d4", + "before": "\u05db\u05d9\u05d1\u05d5\u05d9 \u05dc\u05d0\u05d7\u05e8", + "before_time": "\u05d6\u05de\u05df \u05db\u05d9\u05d1\u05d5\u05d9", + "name": "\u05e9\u05dd" + }, + "description": "\u05d9\u05e6\u05d9\u05e8\u05ea \u05d7\u05d9\u05d9\u05e9\u05df \u05d1\u05d9\u05e0\u05d0\u05e8\u05d9 \u05d4\u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05e0\u05db\u05d1\u05d4 \u05d1\u05d4\u05ea\u05d0\u05dd \u05dc\u05d6\u05de\u05df.", + "title": "\u05d4\u05d5\u05e1\u05e4\u05ea \u05d7\u05d9\u05d9\u05e9\u05df \u05e9\u05e2\u05d5\u05ea \u05d1\u05d9\u05d5\u05dd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "\u05d4\u05e4\u05e2\u05dc\u05d4 \u05dc\u05d0\u05d7\u05e8", + "after_time": "\u05d6\u05de\u05df \u05d4\u05e4\u05e2\u05dc\u05d4", + "before": "\u05db\u05d9\u05d1\u05d5\u05d9 \u05dc\u05d0\u05d7\u05e8", + "before_time": "\u05d6\u05de\u05df \u05db\u05d9\u05d1\u05d5\u05d9" + }, + "description": "\u05d9\u05e6\u05d9\u05e8\u05ea \u05d7\u05d9\u05d9\u05e9\u05df \u05d1\u05d9\u05e0\u05d0\u05e8\u05d9 \u05d4\u05de\u05d5\u05e4\u05e2\u05dc \u05d0\u05d5 \u05e0\u05db\u05d1\u05d4 \u05d1\u05d4\u05ea\u05d0\u05dd \u05dc\u05d6\u05de\u05df." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/hu.json b/homeassistant/components/tod/translations/hu.json new file mode 100644 index 00000000000..28af029f230 --- /dev/null +++ b/homeassistant/components/tod/translations/hu.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "BE ut\u00e1n", + "after_time": "BE id\u0151pont", + "before": "KI ut\u00e1n", + "before_time": "KI id\u0151pont", + "name": "Elnevez\u00e9s" + }, + "description": "\u00c1ll\u00edtsa be, hogy az \u00e9rz\u00e9kel\u0151 mikor kapcsoljon be \u00e9s ki.", + "title": "I\u0151pontok \u00e9rz\u00e9kel\u0151 hozz\u00e1ad\u00e1sa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "BE ut\u00e1n", + "after_time": "BE id\u0151pont", + "before": "KI ut\u00e1n", + "before_time": "KI id\u0151pont" + }, + "description": "\u00c1ll\u00edtsa be, hogy az \u00e9rz\u00e9kel\u0151 mikor kapcsoljon be \u00e9s ki." + } + } + }, + "title": "I\u0151pontok \u00e9rz\u00e9kel\u0151" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/id.json b/homeassistant/components/tod/translations/id.json new file mode 100644 index 00000000000..2ceef6c3a40 --- /dev/null +++ b/homeassistant/components/tod/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Nyala setelah", + "after_time": "Nyala pada", + "before": "Mati setelah", + "before_time": "Mati pada", + "name": "Nama" + }, + "description": "Buat sensor biner yang nyala atau mati tergantung waktu.", + "title": "Tambahkan Sensor Waktu Pada Hari Ini" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Nyala setelah", + "after_time": "Nyala pada", + "before": "Mati setelah", + "before_time": "Mati pada" + }, + "description": "Buat sensor biner yang nyala atau mati tergantung waktu." + } + } + }, + "title": "Sensor Waktu Pada Hari Ini" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/it.json b/homeassistant/components/tod/translations/it.json new file mode 100644 index 00000000000..072cc80f5ff --- /dev/null +++ b/homeassistant/components/tod/translations/it.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Acceso dopo", + "after_time": "Ora di accensione", + "before": "Spento dopo", + "before_time": "Ora di spegnimento", + "name": "Nome" + }, + "description": "Crea un sensore binario che si accende o si spegne a seconda dell'ora.", + "title": "Aggiungi sensore delle ore del giorno" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Acceso dopo", + "after_time": "Ora di accensione", + "before": "Spento dopo", + "before_time": "Ora di spegnimento" + }, + "description": "Crea un sensore binario che si accende o si spegne a seconda dell'ora." + } + } + }, + "title": "Sensore ore del giorno" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/ja.json b/homeassistant/components/tod/translations/ja.json new file mode 100644 index 00000000000..a497f3e3c98 --- /dev/null +++ b/homeassistant/components/tod/translations/ja.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "\u5f8c\u306b\u30aa\u30f3(On after)", + "after_time": "\u30aa\u30f3\u30bf\u30a4\u30e0(On time)", + "before": "\u30aa\u30d5\u5f8c(Off after)", + "before_time": "\u30aa\u30d5\u30bf\u30a4\u30e0(Off time)", + "name": "\u540d\u524d" + }, + "description": "\u30bb\u30f3\u30b5\u30fc\u306e\u30aa\u30f3\u3068\u30aa\u30d5\u3092\u5207\u308a\u66ff\u3048\u308b\u30bf\u30a4\u30df\u30f3\u30b0\u3092\u69cb\u6210\u3057\u307e\u3059\u3002", + "title": "\u65b0\u3057\u3044\u6642\u523b\u30bb\u30f3\u30b5\u30fc" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "\u5f8c\u306b\u30aa\u30f3(On after)", + "after_time": "\u30aa\u30f3\u30bf\u30a4\u30e0(On time)", + "before": "\u30aa\u30d5\u5f8c(Off after)", + "before_time": "\u30aa\u30d5\u30bf\u30a4\u30e0(Off time)" + }, + "description": "\u30bb\u30f3\u30b5\u30fc\u306e\u30aa\u30f3\u3068\u30aa\u30d5\u3092\u5207\u308a\u66ff\u3048\u308b\u30bf\u30a4\u30df\u30f3\u30b0\u3092\u69cb\u6210\u3057\u307e\u3059\u3002" + } + } + }, + "title": "\u6642\u9593\u5e2f\u30bb\u30f3\u30b5\u30fc(Times of the Day Sensor)" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/nl.json b/homeassistant/components/tod/translations/nl.json new file mode 100644 index 00000000000..82f06ea3f4f --- /dev/null +++ b/homeassistant/components/tod/translations/nl.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Aan na", + "after_time": "Op tijd", + "before": "Uit na", + "before_time": "Uit tijd", + "name": "Naam\n" + }, + "description": "Maak een binaire sensor die afhankelijk van de tijd in- of uitgeschakeld wordt.", + "title": "Tijden van de dag sensor toevoegen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Aan na", + "after_time": "Op tijd", + "before": "Uit na", + "before_time": "Uit tijd" + }, + "description": "Maak een binaire sensor die afhankelijk van de tijd in- of uitgeschakeld wordt." + } + } + }, + "title": "Tijd van de dag Sensor" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/no.json b/homeassistant/components/tod/translations/no.json new file mode 100644 index 00000000000..a2cbf7e6427 --- /dev/null +++ b/homeassistant/components/tod/translations/no.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "P\u00e5 etter", + "after_time": "P\u00e5 tide", + "before": "Av etter", + "before_time": "Utenfor arbeidstid", + "name": "Navn" + }, + "description": "Lag en bin\u00e6r sensor som sl\u00e5s av eller p\u00e5 avhengig av tiden.", + "title": "Legg til klokkeslettsensor" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "P\u00e5 etter", + "after_time": "P\u00e5 tide", + "before": "Av etter", + "before_time": "Utenfor arbeidstid" + }, + "description": "Lag en bin\u00e6r sensor som sl\u00e5s av eller p\u00e5 avhengig av tiden." + } + } + }, + "title": "Tidssensor for dagen" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/pl.json b/homeassistant/components/tod/translations/pl.json new file mode 100644 index 00000000000..8b03a798f5c --- /dev/null +++ b/homeassistant/components/tod/translations/pl.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "W\u0142\u0105cz po", + "after_time": "Czas w\u0142\u0105czenia", + "before": "Wy\u0142\u0105cz po", + "before_time": "Czas wy\u0142\u0105czenia", + "name": "Nazwa" + }, + "description": "Utw\u00f3rz sensor binarny, kt\u00f3ry w\u0142\u0105cza si\u0119 lub wy\u0142\u0105cza w zale\u017cno\u015bci od czasu.", + "title": "Dodaj sensor p\u00f3r dnia" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "W\u0142\u0105cz po", + "after_time": "Czas w\u0142\u0105czenia", + "before": "Wy\u0142\u0105cz po", + "before_time": "Czas wy\u0142\u0105czenia" + }, + "description": "Utw\u00f3rz sensor binarny, kt\u00f3ry w\u0142\u0105cza si\u0119 lub wy\u0142\u0105cza w zale\u017cno\u015bci od czasu." + } + } + }, + "title": "Sensor p\u00f3r dnia" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/pt-BR.json b/homeassistant/components/tod/translations/pt-BR.json new file mode 100644 index 00000000000..e9784076fd7 --- /dev/null +++ b/homeassistant/components/tod/translations/pt-BR.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Ligado depois", + "after_time": "Ligado na hora", + "before": "Desligado depois", + "before_time": "Desligado na hora", + "name": "Nome" + }, + "description": "Crie um sensor bin\u00e1rio que liga ou desliga dependendo do tempo.", + "title": "Adicionar sensor de horas do dia" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Ligado depois", + "after_time": "Ligado na hora", + "before": "Desligado depois", + "before_time": "Desligado na hora" + }, + "description": "Crie um sensor bin\u00e1rio que liga ou desliga dependendo do tempo." + } + } + }, + "title": "Sensor de horas do dia" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/ru.json b/homeassistant/components/tod/translations/ru.json new file mode 100644 index 00000000000..eda3e4efd77 --- /dev/null +++ b/homeassistant/components/tod/translations/ru.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435", + "after_time": "\u0412\u0440\u0435\u043c\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", + "before": "\u0412\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435", + "before_time": "\u0412\u0440\u0435\u043c\u044f \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0411\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.", + "title": "\u0412\u0440\u0435\u043c\u044f \u0441\u0443\u0442\u043e\u043a" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435", + "after_time": "\u0412\u0440\u0435\u043c\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", + "before": "\u0412\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435", + "before_time": "\u0412\u0440\u0435\u043c\u044f \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + }, + "description": "\u0411\u0438\u043d\u0430\u0440\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438." + } + } + }, + "title": "\u0412\u0440\u0435\u043c\u044f \u0441\u0443\u0442\u043e\u043a" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/tr.json b/homeassistant/components/tod/translations/tr.json new file mode 100644 index 00000000000..e2f04757369 --- /dev/null +++ b/homeassistant/components/tod/translations/tr.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "Daha sonra", + "after_time": "Vaktinde", + "before": "Sonra kapat", + "before_time": "Kapatma zaman\u0131", + "name": "Ad" + }, + "description": "Zamana ba\u011fl\u0131 olarak a\u00e7\u0131l\u0131p kapanan bir ikili sens\u00f6r olu\u015fturun.", + "title": "G\u00fcn\u00fcn Saatleri Sens\u00f6r\u00fc Ekleyin" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "Daha sonra", + "after_time": "Vaktinde", + "before": "Sonra kapat", + "before_time": "Kapatma zaman\u0131" + }, + "description": "Zamana ba\u011fl\u0131 olarak a\u00e7\u0131l\u0131p kapanan bir ikili sens\u00f6r olu\u015fturun." + } + } + }, + "title": "G\u00fcn\u00fcn Saatleri Sens\u00f6r\u00fc" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/zh-Hans.json b/homeassistant/components/tod/translations/zh-Hans.json new file mode 100644 index 00000000000..1c0b6ba1597 --- /dev/null +++ b/homeassistant/components/tod/translations/zh-Hans.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "\u5728\u6b64\u65f6\u95f4\u540e\u6253\u5f00", + "after_time": "\u6253\u5f00\u65f6\u95f4", + "before": "\u5728\u6b64\u65f6\u95f4\u540e\u5173\u95ed", + "before_time": "\u5173\u95ed\u65f6\u95f4", + "name": "\u540d\u79f0" + }, + "description": "\u521b\u5efa\u6839\u636e\u65f6\u95f4\u6539\u53d8\u5f00\u5173\u72b6\u6001\u7684\u4e8c\u5143\u4f20\u611f\u5668\u3002", + "title": "\u6dfb\u52a0\u65f6\u95f4\u8303\u56f4\u4f20\u611f\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "\u5728\u6b64\u65f6\u95f4\u540e\u6253\u5f00", + "after_time": "\u6253\u5f00\u65f6\u95f4", + "before": "\u5728\u6b64\u65f6\u95f4\u540e\u5173\u95ed", + "before_time": "\u5173\u95ed\u65f6\u95f4" + }, + "description": "\u521b\u5efa\u6839\u636e\u65f6\u95f4\u6539\u53d8\u5f00\u5173\u72b6\u6001\u7684\u4e8c\u5143\u4f20\u611f\u5668\u3002" + } + } + }, + "title": "\u65f6\u95f4\u8303\u56f4\u4f20\u611f\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/tod/translations/zh-Hant.json b/homeassistant/components/tod/translations/zh-Hant.json new file mode 100644 index 00000000000..0a47c33a318 --- /dev/null +++ b/homeassistant/components/tod/translations/zh-Hant.json @@ -0,0 +1,31 @@ +{ + "config": { + "step": { + "user": { + "data": { + "after": "\u958b\u555f\u6642\u9577\u5f8c", + "after_time": "\u958b\u555f\u6642\u9593", + "before": "\u95dc\u9589\u6642\u9577\u5f8c", + "before_time": "\u95dc\u9589\u6642\u9593", + "name": "\u540d\u7a31" + }, + "description": "\u65b0\u589e\u6839\u64da\u6642\u9593\u6c7a\u5b9a\u958b\u95dc\u4e4b\u6642\u9593\u611f\u6e2c\u5668\u3002", + "title": "\u65b0\u589e\u6bcf\u65e5\u5b9a\u6642\u611f\u6e2c\u5668" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "after": "\u958b\u555f\u6642\u9577\u5f8c", + "after_time": "\u958b\u555f\u6642\u9593", + "before": "\u95dc\u9589\u6642\u9577\u5f8c", + "before_time": "\u95dc\u9589\u6642\u9593" + }, + "description": "\u65b0\u589e\u6839\u64da\u6642\u9593\u6c7a\u5b9a\u958b\u95dc\u4e4b\u6642\u9593\u611f\u6e2c\u5668\u3002" + } + } + }, + "title": "\u6bcf\u65e5\u5b9a\u6642\u611f\u6e2c\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 4582b1de0f0..b1e15e42221 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -1,18 +1,21 @@ """Support for Todoist task management (https://todoist.com).""" from __future__ import annotations -from datetime import datetime, timedelta, timezone +from datetime import date, datetime, timedelta, timezone import logging from todoist.api import TodoistAPI import voluptuous as vol -from homeassistant.components.calendar import PLATFORM_SCHEMA, CalendarEventDevice +from homeassistant.components.calendar import ( + PLATFORM_SCHEMA, + CalendarEntity, + CalendarEvent, +) from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN from homeassistant.core import HomeAssistant, ServiceCall import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt @@ -28,7 +31,6 @@ from .const import ( CONF_PROJECT_LABEL_WHITELIST, CONF_PROJECT_WHITELIST, CONTENT, - DATETIME, DESCRIPTION, DOMAIN, DUE, @@ -102,7 +104,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -SCAN_INTERVAL = timedelta(minutes=15) +SCAN_INTERVAL = timedelta(minutes=1) def setup_platform( @@ -136,7 +138,7 @@ def setup_platform( # Project is an object, not a dict! # Because of that, we convert what we need to a dict. project_data = {CONF_NAME: project[NAME], CONF_ID: project[ID]} - project_devices.append(TodoistProjectDevice(hass, project_data, labels, api)) + project_devices.append(TodoistProjectEntity(hass, project_data, labels, api)) # Cache the names so we can easily look up name->ID. project_id_lookup[project[NAME].lower()] = project[ID] @@ -166,7 +168,7 @@ def setup_platform( # Create the custom project and add it to the devices array. project_devices.append( - TodoistProjectDevice( + TodoistProjectEntity( hass, project, labels, @@ -176,7 +178,6 @@ def setup_platform( project_id_filter, ) ) - add_entities(project_devices) def handle_new_task(call: ServiceCall) -> None: @@ -271,7 +272,7 @@ def _parse_due_date(data: DueDate, timezone_offset: int) -> datetime | None: return dt.as_utc(nowtime) -class TodoistProjectDevice(CalendarEventDevice): +class TodoistProjectEntity(CalendarEntity): """A device for getting the next Task from a Todoist Project.""" def __init__( @@ -284,7 +285,7 @@ class TodoistProjectDevice(CalendarEventDevice): whitelisted_labels=None, whitelisted_projects=None, ): - """Create the Todoist Calendar Event Device.""" + """Create the Todoist Calendar Entity.""" self.data = TodoistProjectData( data, labels, @@ -297,9 +298,9 @@ class TodoistProjectDevice(CalendarEventDevice): self._name = data[CONF_NAME] @property - def event(self): + def event(self) -> CalendarEvent: """Return the next upcoming event.""" - return self.data.event + return self.data.calendar_event @property def name(self): @@ -314,7 +315,12 @@ class TodoistProjectDevice(CalendarEventDevice): task[SUMMARY] for task in self.data.all_project_tasks ] - async def async_get_events(self, hass, start_date, end_date): + async def async_get_events( + self, + hass: HomeAssistant, + start_date: datetime, + end_date: datetime, + ) -> list[CalendarEvent]: """Get all events in a specific time frame.""" return await self.data.async_get_events(hass, start_date, end_date) @@ -336,7 +342,7 @@ class TodoistProjectDevice(CalendarEventDevice): class TodoistProjectData: """ - Class used by the Task Device service object to hold all Todoist Tasks. + Class used by the Task Entity service object to hold all Todoist Tasks. This is analogous to the GoogleCalendarData found in the Google Calendar component. @@ -409,6 +415,22 @@ class TodoistProjectData: else: self._project_id_whitelist = [] + @property + def calendar_event(self) -> CalendarEvent | None: + """Return the next upcoming calendar event.""" + if not self.event: + return None + if not self.event.get(END) or self.event.get(ALL_DAY): + start = self.event[START].date() + return CalendarEvent( + summary=self.event[SUMMARY], + start=start, + end=start + timedelta(days=1), + ) + return CalendarEvent( + summary=self.event[SUMMARY], start=self.event[START], end=self.event[END] + ) + def create_todoist_task(self, data): """ Create a dictionary based on a Task passed from the Todoist API. @@ -566,7 +588,7 @@ class TodoistProjectData: if task["due"] is None: continue # @NOTE: _parse_due_date always returns the date in UTC time. - due_date = _parse_due_date( + due_date: datetime | None = _parse_due_date( task["due"], self._api.state["user"]["tz_info"]["hours"] ) if not due_date: @@ -580,20 +602,16 @@ class TodoistProjectData: ) if start_date < due_date < end_date: + due_date_value: datetime | date = due_date if due_date == midnight: # If the due date has no time data, return just the date so that it # will render correctly as an all day event on a calendar. - due_date_value = due_date.strftime("%Y-%m-%d") - else: - due_date_value = due_date.isoformat() - event = { - "uid": task["id"], - "title": task["content"], - "start": due_date_value, - "end": due_date_value, - "allDay": True, - "summary": task["content"], - } + due_date_value = due_date.date() + event = CalendarEvent( + summary=task["content"], + start=due_date_value, + end=due_date_value, + ) events.append(event) return events @@ -644,22 +662,10 @@ class TodoistProjectData: project_tasks.remove(best_task) self.all_project_tasks.append(best_task) - self.event = self.all_project_tasks[0] - - # Convert datetime to a string again - if self.event is not None: - if self.event[START] is not None: - self.event[START] = { - DATETIME: self.event[START].strftime(DATE_STR_FORMAT) - } - if self.event[END] is not None: - self.event[END] = {DATETIME: self.event[END].strftime(DATE_STR_FORMAT)} - else: - # Home Assistant gets cranky if a calendar event never ends - # Let's set our "due date" to tomorrow - self.event[END] = { - DATETIME: (datetime.utcnow() + timedelta(days=1)).strftime( - DATE_STR_FORMAT - ) - } + event = self.all_project_tasks[0] + if event is None or event[START] is None: + _LOGGER.debug("No valid event or event start for %s", self._name) + self.event = None + return + self.event = event _LOGGER.debug("Updated %s", self._name) diff --git a/homeassistant/components/tolo/climate.py b/homeassistant/components/tolo/climate.py index 4c02cdb74dc..01955f62a89 100644 --- a/homeassistant/components/tolo/climate.py +++ b/homeassistant/components/tolo/climate.py @@ -1,27 +1,17 @@ """TOLO Sauna climate controls (main sauna control).""" - from __future__ import annotations from typing import Any from tololib.const import Calefaction -from homeassistant.components.climate import ( - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - ClimateEntity, -) +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_DRY, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, FAN_OFF, FAN_ON, - HVAC_MODE_DRY, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS @@ -52,7 +42,7 @@ class SaunaClimate(ToloSaunaCoordinatorEntity, ClimateEntity): """Sauna climate control.""" _attr_fan_modes = [FAN_ON, FAN_OFF] - _attr_hvac_modes = [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_DRY] + _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT, HVACMode.DRY] _attr_max_humidity = DEFAULT_MAX_HUMIDITY _attr_max_temp = DEFAULT_MAX_TEMP _attr_min_humidity = DEFAULT_MIN_HUMIDITY @@ -60,7 +50,9 @@ class SaunaClimate(ToloSaunaCoordinatorEntity, ClimateEntity): _attr_name = "Sauna Climate" _attr_precision = PRECISION_WHOLE _attr_supported_features = ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY | SUPPORT_FAN_MODE + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_HUMIDITY + | ClimateEntityFeature.FAN_MODE ) _attr_target_temperature_step = 1 _attr_temperature_unit = TEMP_CELSIUS @@ -94,28 +86,28 @@ class SaunaClimate(ToloSaunaCoordinatorEntity, ClimateEntity): return self.coordinator.data.settings.target_humidity @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Get current HVAC mode.""" if self.coordinator.data.status.power_on: - return HVAC_MODE_HEAT + return HVACMode.HEAT if ( not self.coordinator.data.status.power_on and self.coordinator.data.status.fan_on ): - return HVAC_MODE_DRY - return HVAC_MODE_OFF + return HVACMode.DRY + return HVACMode.OFF @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Execute HVAC action.""" if self.coordinator.data.status.calefaction == Calefaction.HEAT: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if self.coordinator.data.status.calefaction == Calefaction.KEEP: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if self.coordinator.data.status.calefaction == Calefaction.INACTIVE: if self.coordinator.data.status.fan_on: - return CURRENT_HVAC_DRY - return CURRENT_HVAC_OFF + return HVACAction.DRYING + return HVACAction.OFF return None @property @@ -125,13 +117,13 @@ class SaunaClimate(ToloSaunaCoordinatorEntity, ClimateEntity): return FAN_ON return FAN_OFF - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set HVAC mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: self._set_power_and_fan(False, False) - if hvac_mode == HVAC_MODE_HEAT: + if hvac_mode == HVACMode.HEAT: self._set_power_and_fan(True, False) - if hvac_mode == HVAC_MODE_DRY: + if hvac_mode == HVACMode.DRY: self._set_power_and_fan(False, True) def set_fan_mode(self, fan_mode: str) -> None: diff --git a/homeassistant/components/tolo/light.py b/homeassistant/components/tolo/light.py index f58f7e7b8c9..715a1327e4b 100644 --- a/homeassistant/components/tolo/light.py +++ b/homeassistant/components/tolo/light.py @@ -1,10 +1,9 @@ """TOLO Sauna light controls.""" - from __future__ import annotations from typing import Any -from homeassistant.components.light import COLOR_MODE_ONOFF, LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -26,8 +25,9 @@ async def async_setup_entry( class ToloLight(ToloSaunaCoordinatorEntity, LightEntity): """Sauna light control.""" + _attr_color_mode = ColorMode.ONOFF _attr_name = "Sauna Light" - _attr_supported_color_modes = {COLOR_MODE_ONOFF} + _attr_supported_color_modes = {ColorMode.ONOFF} def __init__( self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry diff --git a/homeassistant/components/tomorrowio/__init__.py b/homeassistant/components/tomorrowio/__init__.py index 8074949a888..d8decc1aea3 100644 --- a/homeassistant/components/tomorrowio/__init__.py +++ b/homeassistant/components/tomorrowio/__init__.py @@ -25,7 +25,7 @@ from homeassistant.const import ( CONF_LONGITUDE, CONF_NAME, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo @@ -84,7 +84,10 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = [SENSOR_DOMAIN, WEATHER_DOMAIN] -def _set_update_interval(hass: HomeAssistant, current_entry: ConfigEntry) -> timedelta: +@callback +def async_set_update_interval( + hass: HomeAssistant, current_entry: ConfigEntry +) -> timedelta: """Recalculate update_interval based on existing Tomorrow.io instances and update them.""" api_calls = 2 # We check how many Tomorrow.io configured instances are using the same API key and @@ -114,6 +117,61 @@ def _set_update_interval(hass: HomeAssistant, current_entry: ConfigEntry) -> tim return interval +@callback +def async_migrate_entry_from_climacell( + hass: HomeAssistant, + dev_reg: dr.DeviceRegistry, + entry: ConfigEntry, + device: dr.DeviceEntry, +) -> None: + """Migrate a config entry from a Climacell entry.""" + # Remove the old config entry ID from the entry data so we don't try this again + # on the next setup + data = entry.data.copy() + old_config_entry_id = data.pop("old_config_entry_id") + hass.config_entries.async_update_entry(entry, data=data) + _LOGGER.debug( + ( + "Setting up imported climacell entry %s for the first time as " + "tomorrowio entry %s" + ), + old_config_entry_id, + entry.entry_id, + ) + + ent_reg = er.async_get(hass) + for entity_entry in er.async_entries_for_config_entry(ent_reg, old_config_entry_id): + old_platform = entity_entry.platform + # In case the API key has changed due to a V3 -> V4 change, we need to + # generate the new entity's unique ID + new_unique_id = ( + f"{entry.data[CONF_API_KEY]}_" + f"{'_'.join(entity_entry.unique_id.split('_')[1:])}" + ) + ent_reg.async_update_entity_platform( + entity_entry.entity_id, + DOMAIN, + new_unique_id=new_unique_id, + new_config_entry_id=entry.entry_id, + new_device_id=device.id, + ) + assert entity_entry + _LOGGER.debug( + "Migrated %s from %s to %s", + entity_entry.entity_id, + old_platform, + DOMAIN, + ) + + # We only have one device in the registry but we will do a loop just in case + for old_device in dr.async_entries_for_config_entry(dev_reg, old_config_entry_id): + if old_device.name_by_user: + dev_reg.async_update_device(device.id, name_by_user=old_device.name_by_user) + + # Remove the old config entry and now the entry is fully migrated + hass.async_create_task(hass.config_entries.async_remove(old_config_entry_id)) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tomorrow.io API from a config entry.""" hass.data.setdefault(DOMAIN, {}) @@ -137,66 +195,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # entities, we need to remove each old entity, creating a new entity in its place # but attached to this entry. if entry.source == SOURCE_IMPORT and "old_config_entry_id" in entry.data: - # Remove the old config entry ID from the entry data so we don't try this again - # on the next setup - data = entry.data.copy() - old_config_entry_id = data.pop("old_config_entry_id") - hass.config_entries.async_update_entry(entry, data=data) - _LOGGER.debug( - ( - "Setting up imported climacell entry %s for the first time as " - "tomorrowio entry %s" - ), - old_config_entry_id, - entry.entry_id, - ) - - ent_reg = er.async_get(hass) - for entity_entry in er.async_entries_for_config_entry( - ent_reg, old_config_entry_id - ): - _LOGGER.debug("Removing %s", entity_entry.entity_id) - ent_reg.async_remove(entity_entry.entity_id) - # In case the API key has changed due to a V3 -> V4 change, we need to - # generate the new entity's unique ID - new_unique_id = ( - f"{entry.data[CONF_API_KEY]}_" - f"{'_'.join(entity_entry.unique_id.split('_')[1:])}" - ) - _LOGGER.debug( - "Re-creating %s for the new config entry", entity_entry.entity_id - ) - # We will precreate the entity so that any customizations can be preserved - new_entity_entry = ent_reg.async_get_or_create( - entity_entry.domain, - DOMAIN, - new_unique_id, - suggested_object_id=entity_entry.entity_id.split(".")[1], - disabled_by=entity_entry.disabled_by, - config_entry=entry, - original_name=entity_entry.original_name, - original_icon=entity_entry.original_icon, - ) - _LOGGER.debug("Re-created %s", new_entity_entry.entity_id) - # If there are customizations on the old entity, apply them to the new one - if entity_entry.name or entity_entry.icon: - ent_reg.async_update_entity( - new_entity_entry.entity_id, - name=entity_entry.name, - icon=entity_entry.icon, - ) - - # We only have one device in the registry but we will do a loop just in case - for old_device in dr.async_entries_for_config_entry( - dev_reg, old_config_entry_id - ): - if old_device.name_by_user: - dev_reg.async_update_device( - device.id, name_by_user=old_device.name_by_user - ) - - # Remove the old config entry and now the entry is fully migrated - hass.async_create_task(hass.config_entries.async_remove(old_config_entry_id)) + async_migrate_entry_from_climacell(hass, dev_reg, entry, device) api = TomorrowioV4( entry.data[CONF_API_KEY], @@ -210,7 +209,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, entry, api, - _set_update_interval(hass, entry), + async_set_update_interval(hass, entry), ) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/tomorrowio/config_flow.py b/homeassistant/components/tomorrowio/config_flow.py index bde49ed3fe5..a0fa7b0b34c 100644 --- a/homeassistant/components/tomorrowio/config_flow.py +++ b/homeassistant/components/tomorrowio/config_flow.py @@ -27,7 +27,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.selector import selector +from homeassistant.helpers.selector import LocationSelector, LocationSelectorConfig from .const import ( AUTO_MIGRATION_MESSAGE, @@ -78,7 +78,7 @@ def _get_config_schema( vol.Required( CONF_LOCATION, default=default_location, - ): selector({"location": {"radius": False}}), + ): LocationSelector(LocationSelectorConfig(radius=False)), }, ) diff --git a/homeassistant/components/tomorrowio/manifest.json b/homeassistant/components/tomorrowio/manifest.json index 6fc8a2f12ce..a577ec517c1 100644 --- a/homeassistant/components/tomorrowio/manifest.json +++ b/homeassistant/components/tomorrowio/manifest.json @@ -3,7 +3,7 @@ "name": "Tomorrow.io", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tomorrowio", - "requirements": ["pytomorrowio==0.1.0"], + "requirements": ["pytomorrowio==0.3.3"], "codeowners": ["@raman325"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/tomorrowio/translations/bg.json b/homeassistant/components/tomorrowio/translations/bg.json new file mode 100644 index 00000000000..783408dab84 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/bg.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447", + "latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430", + "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", + "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/ca.json b/homeassistant/components/tomorrowio/translations/ca.json new file mode 100644 index 00000000000..fc351430ffb --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/ca.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_api_key": "Clau API inv\u00e0lida", + "rate_limited": "Freq\u00fc\u00e8ncia limitada temporalment, torna-ho a provar m\u00e9s tard.", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "location": "Ubicaci\u00f3", + "longitude": "Longitud", + "name": "Nom" + }, + "description": "Per obtenir una clau API, registra't a [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. entre previsions de NowCast" + }, + "description": "Si decideixes activar l'entitat de previsi\u00f3 `nowcast`, podr\u00e0s configurar l'interval en minuts entre cada previsi\u00f3. El nombre de previsions proporcionades dep\u00e8n d'aquest interval de minuts.", + "title": "Actualitza les opcions de Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/cs.json b/homeassistant/components/tomorrowio/translations/cs.json new file mode 100644 index 00000000000..6b50656cd00 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/cs.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "location": "Um\u00edst\u011bn\u00ed" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/de.json b/homeassistant/components/tomorrowio/translations/de.json new file mode 100644 index 00000000000..03739ce5c2f --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/de.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "rate_limited": "Aktuelle Aktualisierungsrate gedrosselt, bitte versuche es sp\u00e4ter erneut.", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "location": "Standort", + "longitude": "L\u00e4ngengrad", + "name": "Name" + }, + "description": "Um einen API-Schl\u00fcssel zu erhalten, melde dich bei [Tomorrow.io] (https://app.tomorrow.io/signup) an." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Minuten zwischen den NowCast Kurzvorhersagen" + }, + "description": "Wenn du die Vorhersage-Entitit\u00e4t \"Kurzvorhersage\" aktivierst, kannst du die Anzahl der Minuten zwischen den einzelnen Vorhersagen konfigurieren. Die Anzahl der bereitgestellten Vorhersagen h\u00e4ngt von der Anzahl der zwischen den Vorhersagen gew\u00e4hlten Minuten ab.", + "title": "Tomorrow.io Optionen aktualisieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/el.json b/homeassistant/components/tomorrowio/translations/el.json new file mode 100644 index 00000000000..28e3f56c379 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/el.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", + "rate_limited": "\u0391\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae \u03b7 \u03c4\u03b9\u03bc\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b5\u03c1\u03b9\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "latitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2", + "location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03bc\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "\u0393\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API, \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "\u0395\u03bb\u03ac\u03c7. \u039c\u03b5\u03c4\u03b1\u03be\u03cd NowCast Forecasts" + }, + "description": "\u0395\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b5\u03c8\u03b7\u03c2 \"nowcast\", \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03ba\u03ac\u03b8\u03b5 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b5\u03c8\u03b7\u03c2. \u039f \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03c9\u03bd \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03c0\u03c1\u03bf\u03b2\u03bb\u03ad\u03c8\u03b5\u03c9\u03bd \u03b5\u03be\u03b1\u03c1\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03c0\u03c1\u03bf\u03b2\u03bb\u03ad\u03c8\u03b5\u03c9\u03bd.", + "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/en.json b/homeassistant/components/tomorrowio/translations/en.json index 7c653b00574..103f1c81679 100644 --- a/homeassistant/components/tomorrowio/translations/en.json +++ b/homeassistant/components/tomorrowio/translations/en.json @@ -11,6 +11,7 @@ "data": { "api_key": "API Key", "latitude": "Latitude", + "location": "Location", "longitude": "Longitude", "name": "Name" }, diff --git a/homeassistant/components/tomorrowio/translations/et.json b/homeassistant/components/tomorrowio/translations/et.json new file mode 100644 index 00000000000..7ac38239349 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/et.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_api_key": "Vigane API v\u00f5ti", + "rate_limited": "Hetkel on m\u00e4\u00e4r piiratud, proovi hiljem uuesti.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "location": "Asukoht", + "longitude": "Pikkuskraad", + "name": "Nimi" + }, + "description": "API v\u00f5tme saamiseks registreeru aadressil [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Minuteid NowCasti prognooside vahel" + }, + "description": "Kui otsustad lubada olemi \"nowcast\", saad seadistada iga prognoosi vahelise minutite arvu. Esitatud prognooside arv s\u00f5ltub prognooside vahel valitud minutite arvust.", + "title": "V\u00e4rskenda Tomorrow.io valikuid" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/fr.json b/homeassistant/components/tomorrowio/translations/fr.json new file mode 100644 index 00000000000..bcb97eb3fc5 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/fr.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_api_key": "Cl\u00e9 d'API non valide", + "rate_limited": "Nombre maximal de tentatives de connexion d\u00e9pass\u00e9, veuillez r\u00e9essayer ult\u00e9rieurement.", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "location": "Emplacement", + "longitude": "Longitude", + "name": "Nom" + }, + "description": "Pour obtenir une cl\u00e9 d'API, inscrivez-vous sur [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. entre les pr\u00e9visions NowCast" + }, + "description": "Si vous choisissez d'activer l'entit\u00e9 de pr\u00e9vision \u00ab\u00a0nowcast\u00a0\u00bb, vous pouvez configurer le nombre de minutes entre chaque pr\u00e9vision. Le nombre de pr\u00e9visions fournies d\u00e9pend du nombre de minutes choisies entre les pr\u00e9visions.", + "title": "Mettre \u00e0 jour les options de Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/he.json b/homeassistant/components/tomorrowio/translations/he.json new file mode 100644 index 00000000000..20b48520f18 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/he.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", + "location": "\u05de\u05d9\u05e7\u05d5\u05dd", + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", + "name": "\u05e9\u05dd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/hu.json b/homeassistant/components/tomorrowio/translations/hu.json new file mode 100644 index 00000000000..8f90392234e --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/hu.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", + "rate_limited": "Jelenleg korl\u00e1tozott a hozz\u00e1f\u00e9r\u00e9s, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja meg k\u00e9s\u0151bb \u00fajra.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "location": "Elhelyezked\u00e9s", + "longitude": "Hossz\u00fas\u00e1g", + "name": "Elnevez\u00e9s" + }, + "description": "API-kulcs beszerz\u00e9s\u00e9hez regisztr\u00e1ljon a [Tomorrow.io] (https://app.tomorrow.io/signup) oldalon." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Percek NowCast el\u0151rejelz\u00e9sek k\u00f6z\u00f6tt" + }, + "description": "Ha enged\u00e9lyezi a 'nowcast' id\u0151j\u00e1r\u00e1st, be\u00e1ll\u00edthatja az egyes el\u0151rejelz\u00e9sek k\u00f6z\u00f6tt eltelt percek sz\u00e1m\u00e1t. Az el\u0151rejelz\u00e9sek sz\u00e1ma az el\u0151rejelz\u00e9sek k\u00f6z\u00f6tt v\u00e1lasztott percek sz\u00e1m\u00e1t\u00f3l f\u00fcgg.", + "title": "Tomorrow.io be\u00e1ll\u00edt\u00e1sok friss\u00edt\u00e9se" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/id.json b/homeassistant/components/tomorrowio/translations/id.json new file mode 100644 index 00000000000..b428648e799 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/id.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid", + "rate_limited": "Saat ini tingkatnya dibatasi, coba lagi nanti.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "location": "Lokasi", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Untuk mendapatkan kunci API, daftar di [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Jarak Interval Prakiraan NowCast dalam Menit" + }, + "description": "Jika Anda memilih untuk mengaktifkan entitas prakiraan 'nowcast', Anda dapat mengonfigurasi jumlah menit antar-prakiraan. Jumlah prakiraan yang diberikan tergantung pada jumlah menit yang dipilih antar-prakiraan.", + "title": "Perbarui Opsi Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/it.json b/homeassistant/components/tomorrowio/translations/it.json new file mode 100644 index 00000000000..9a79896a3d8 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/it.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_api_key": "Chiave API non valida", + "rate_limited": "Attualmente la tariffa \u00e8 limitata, riprova pi\u00f9 tardi.", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "location": "Posizione", + "longitude": "Logitudine", + "name": "Nome" + }, + "description": "Per ottenere una chiave API, registrati su [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. tra le previsioni di NowCast" + }, + "description": "Se scegli di abilitare l'entit\u00e0 di previsione `nowcast`, puoi configurare il numero di minuti tra ciascuna previsione. Il numero di previsioni fornite dipende dal numero di minuti scelti tra le previsioni.", + "title": "Aggiorna le opzioni di Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/ja.json b/homeassistant/components/tomorrowio/translations/ja.json new file mode 100644 index 00000000000..17d31f74214 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/ja.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc", + "rate_limited": "\u73fe\u5728\u30ec\u30fc\u30c8\u304c\u5236\u9650\u3055\u308c\u3066\u3044\u307e\u3059\u306e\u3067\u3001\u5f8c\u3067\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002", + "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" + }, + "step": { + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "latitude": "\u7def\u5ea6", + "location": "\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3", + "longitude": "\u7d4c\u5ea6", + "name": "\u540d\u524d" + }, + "description": "API\u30ad\u30fc\u3092\u53d6\u5f97\u3059\u308b\u306b\u306f\u3001 [Tomorrow.io](https://app.tomorrow.io/signup) \u306b\u30b5\u30a4\u30f3\u30a2\u30c3\u30d7\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "\u6700\u5c0f: Between NowCast Forecasts" + }, + "description": "`nowcast` forecast(\u4e88\u6e2c) \u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u6709\u52b9\u306b\u3059\u308b\u3053\u3068\u3092\u9078\u629e\u3057\u305f\u5834\u5408\u3001\u5404\u4e88\u6e2c\u9593\u306e\u5206\u6570\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u63d0\u4f9b\u3055\u308c\u308bforecast(\u4e88\u6e2c)\u306e\u6570\u306f\u3001forecast(\u4e88\u6e2c)\u306e\u9593\u306b\u9078\u629e\u3057\u305f\u5206\u6570\u306b\u4f9d\u5b58\u3057\u307e\u3059\u3002", + "title": "Tomorrow.io\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u66f4\u65b0" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/nl.json b/homeassistant/components/tomorrowio/translations/nl.json new file mode 100644 index 00000000000..d1efb7d75c3 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/nl.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_api_key": "Ongeldige API-sleutel", + "rate_limited": "Aantal aanvragen bereikt, probeer later opnieuw", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "location": "Locatie", + "longitude": "Lengtegraad", + "name": "Naam" + }, + "description": "Om een API sleutel te krijgen, meld je aan bij [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. Tussen NowCast Voorspellingen" + }, + "description": "Indien u ervoor kiest om de `nowcast` voorspellingsentiteit in te schakelen, kan u het aantal minuten tussen elke voorspelling configureren. Het aantal voorspellingen hangt af van het aantal gekozen minuten tussen de voorspellingen.", + "title": "Update Tomorrow.io Opties" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/no.json b/homeassistant/components/tomorrowio/translations/no.json new file mode 100644 index 00000000000..bf366895a70 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/no.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "rate_limited": "For \u00f8yeblikket prisbegrenset, pr\u00f8v igjen senere.", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "location": "Plassering", + "longitude": "Lengdegrad", + "name": "Navn" + }, + "description": "For \u00e5 f\u00e5 en API-n\u00f8kkel, registrer deg p\u00e5 [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. Mellom NowCast-prognoser" + }, + "description": "Hvis du velger \u00e5 aktivere prognoseenheten \"nowcast\", kan du konfigurere antall minutter mellom hver prognose. Antallet prognoser som gis avhenger av antall minutter valgt mellom prognosene.", + "title": "Oppdater Tomorrow.io-alternativer" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/pl.json b/homeassistant/components/tomorrowio/translations/pl.json new file mode 100644 index 00000000000..4637a553aa4 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/pl.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_api_key": "Nieprawid\u0142owy klucz API", + "rate_limited": "Przekroczono limit, spr\u00f3buj ponownie p\u00f3\u017aniej.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "location": "Lokalizacja", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "name": "Nazwa" + }, + "description": "Aby uzyska\u0107 klucz API, zarejestruj si\u0119 na [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Czas (min) mi\u0119dzy prognozami NowCast" + }, + "description": "Je\u015bli zdecydujesz si\u0119 w\u0142\u0105czy\u0107 encj\u0119 prognozy \u201enowcast\u201d, mo\u017cesz skonfigurowa\u0107 liczb\u0119 minut mi\u0119dzy ka\u017cd\u0105 prognoz\u0105. Liczba dostarczonych prognoz zale\u017cy od liczby minut wybranych mi\u0119dzy prognozami.", + "title": "Zaktualizuj opcje Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/pt-BR.json b/homeassistant/components/tomorrowio/translations/pt-BR.json new file mode 100644 index 00000000000..83f78e31b8e --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/pt-BR.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_api_key": "Chave de API inv\u00e1lida", + "rate_limited": "Taxa atualmente limitada. Tente novamente mais tarde.", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Chave da API", + "latitude": "Latitude", + "location": "Localiza\u00e7\u00e3o", + "longitude": "Longitude", + "name": "Nome" + }, + "description": "Para obter uma chave de API, inscreva-se em [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "Min. Entre Previs\u00f5es NowCast" + }, + "description": "Se voc\u00ea optar por ativar a entidade de previs\u00e3o `nowcast`, poder\u00e1 configurar o n\u00famero de minutos entre cada previs\u00e3o. O n\u00famero de previs\u00f5es fornecidas depende do n\u00famero de minutos escolhidos entre as previs\u00f5es.", + "title": "Atualizar op\u00e7\u00f5es do Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/ru.json b/homeassistant/components/tomorrowio/translations/ru.json new file mode 100644 index 00000000000..08e24c5a47a --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/ru.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "rate_limited": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u043b\u044e\u0447 API, \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044c \u043d\u0430 [Tomorrow.io](https://app.tomorrow.io/signup)." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430\u043c\u0438 NowCast (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + }, + "description": "\u0415\u0441\u043b\u0438 \u0412\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 'nowcast', \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430. \u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u043e\u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430\u043c\u0438.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Tomorrow.io" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.bg.json b/homeassistant/components/tomorrowio/translations/sensor.bg.json new file mode 100644 index 00000000000..1a5c2989702 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.bg.json @@ -0,0 +1,8 @@ +{ + "state": { + "tomorrowio__precipitation_type": { + "rain": "\u0414\u044a\u0436\u0434", + "snow": "\u0421\u043d\u044f\u0433" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.ca.json b/homeassistant/components/tomorrowio/translations/sensor.ca.json new file mode 100644 index 00000000000..161978d1919 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.ca.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Bo", + "hazardous": "Perill\u00f3s", + "moderate": "Moderat", + "unhealthy": "Poc saludable", + "unhealthy_for_sensitive_groups": "No saludable per a grups sensibles", + "very_unhealthy": "Gens saludable" + }, + "tomorrowio__pollen_index": { + "high": "Alt", + "low": "Baix", + "medium": "Mitj\u00e0", + "none": "Cap", + "very_high": "Molt alt", + "very_low": "Molt baix" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Pluja congelada", + "ice_pellets": "Gran\u00eds", + "none": "Cap", + "rain": "Pluja", + "snow": "Neu" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.de.json b/homeassistant/components/tomorrowio/translations/sensor.de.json new file mode 100644 index 00000000000..801833520f7 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.de.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Gut", + "hazardous": "Gef\u00e4hrlich", + "moderate": "M\u00e4\u00dfig", + "unhealthy": "Ungesund", + "unhealthy_for_sensitive_groups": "Ungesund f\u00fcr sensible Gruppen", + "very_unhealthy": "Sehr ungesund" + }, + "tomorrowio__pollen_index": { + "high": "Hoch", + "low": "Niedrig", + "medium": "Mittel", + "none": "Keiner", + "very_high": "Sehr hoch", + "very_low": "Sehr niedrig" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Gefrierender Regen", + "ice_pellets": "Graupel", + "none": "Keiner", + "rain": "Regen", + "snow": "Schnee" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.el.json b/homeassistant/components/tomorrowio/translations/sensor.el.json new file mode 100644 index 00000000000..fd4ca22f8d0 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.el.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "\u039a\u03b1\u03bb\u03cc", + "hazardous": "\u0395\u03c0\u03b9\u03ba\u03af\u03bd\u03b4\u03c5\u03bd\u03bf", + "moderate": "\u039c\u03ad\u03c4\u03c1\u03b9\u03bf", + "unhealthy": "\u0391\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc", + "unhealthy_for_sensitive_groups": "\u0391\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc \u03b3\u03b9\u03b1 \u03b5\u03c5\u03b1\u03af\u03c3\u03b8\u03b7\u03c4\u03b5\u03c2 \u03bf\u03bc\u03ac\u03b4\u03b5\u03c2", + "very_unhealthy": "\u03a0\u03bf\u03bb\u03cd \u0391\u03bd\u03b8\u03c5\u03b3\u03b9\u03b5\u03b9\u03bd\u03cc" + }, + "tomorrowio__pollen_index": { + "high": "\u03a5\u03c8\u03b7\u03bb\u03cc", + "low": "\u03a7\u03b1\u03bc\u03b7\u03bb\u03cc", + "medium": "\u039c\u03b5\u03c3\u03b1\u03af\u03bf", + "none": "\u03a4\u03af\u03c0\u03bf\u03c4\u03b1", + "very_high": "\u03a0\u03bf\u03bb\u03cd \u03c5\u03c8\u03b7\u03bb\u03cc", + "very_low": "\u03a0\u03bf\u03bb\u03cd \u03c7\u03b1\u03bc\u03b7\u03bb\u03cc" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "\u03a0\u03b1\u03b3\u03c9\u03bc\u03ad\u03bd\u03b7 \u03b2\u03c1\u03bf\u03c7\u03ae", + "ice_pellets": "\u03a7\u03b1\u03bb\u03ac\u03b6\u03b9", + "none": "\u03a4\u03af\u03c0\u03bf\u03c4\u03b1", + "rain": "\u0392\u03c1\u03bf\u03c7\u03ae", + "snow": "\u03a7\u03b9\u03cc\u03bd\u03b9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.et.json b/homeassistant/components/tomorrowio/translations/sensor.et.json new file mode 100644 index 00000000000..56482b21e12 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.et.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Hea", + "hazardous": "Ohtlik", + "moderate": "M\u00f5\u00f5dukas", + "unhealthy": "Ebatervislik", + "unhealthy_for_sensitive_groups": "Ebatervislik riskir\u00fchmale", + "very_unhealthy": "V\u00e4ga ebatervislik" + }, + "tomorrowio__pollen_index": { + "high": "K\u00f5rge", + "low": "Madal", + "medium": "Keskmine", + "none": "Saastet pole", + "very_high": "V\u00e4ga k\u00f5rge", + "very_low": "V\u00e4ga madal" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "J\u00e4\u00e4vihm", + "ice_pellets": "J\u00e4\u00e4kruubid", + "none": "Sademeid pole", + "rain": "Vihm", + "snow": "Lumi" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.fr.json b/homeassistant/components/tomorrowio/translations/sensor.fr.json new file mode 100644 index 00000000000..c0e2ee8747d --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.fr.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Bon", + "hazardous": "Dangereux", + "moderate": "Mod\u00e9r\u00e9", + "unhealthy": "Malsain", + "unhealthy_for_sensitive_groups": "Malsain pour les groupes sensibles", + "very_unhealthy": "Tr\u00e8s malsain" + }, + "tomorrowio__pollen_index": { + "high": "\u00c9lev\u00e9", + "low": "Faible", + "medium": "Moyen", + "none": "Nul", + "very_high": "Tr\u00e8s \u00e9lev\u00e9", + "very_low": "Tr\u00e8s faible" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Pluie vergla\u00e7ante", + "ice_pellets": "Gr\u00e9sil", + "none": "Aucune", + "rain": "Pluie", + "snow": "Neige" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.hu.json b/homeassistant/components/tomorrowio/translations/sensor.hu.json new file mode 100644 index 00000000000..3377ecb47dd --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.hu.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "J\u00f3", + "hazardous": "Vesz\u00e9lyes", + "moderate": "M\u00e9rs\u00e9kelt", + "unhealthy": "Eg\u00e9szs\u00e9gtelen", + "unhealthy_for_sensitive_groups": "Eg\u00e9szs\u00e9gtelen \u00e9rz\u00e9keny csoportok sz\u00e1m\u00e1ra", + "very_unhealthy": "Nagyon eg\u00e9szs\u00e9gtelen" + }, + "tomorrowio__pollen_index": { + "high": "Magas", + "low": "Alacsony", + "medium": "K\u00f6zepes", + "none": "Nincs", + "very_high": "Nagyon magas", + "very_low": "Nagyon alacsony" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Havas es\u0151", + "ice_pellets": "\u00d3nos es\u0151", + "none": "Nincs", + "rain": "Es\u0151", + "snow": "Havaz\u00e1s" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.id.json b/homeassistant/components/tomorrowio/translations/sensor.id.json new file mode 100644 index 00000000000..6323cf6beef --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.id.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Bagus", + "hazardous": "Berbahaya", + "moderate": "Sedang", + "unhealthy": "Tidak Sehat", + "unhealthy_for_sensitive_groups": "Tidak Sehat untuk Kelompok Sensitif", + "very_unhealthy": "Sangat Tidak Sehat" + }, + "tomorrowio__pollen_index": { + "high": "Tinggi", + "low": "Rendah", + "medium": "Sedang", + "none": "Tidak Ada", + "very_high": "Sangat Tinggi", + "very_low": "Sangat Rendah" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Hujan Beku", + "ice_pellets": "Hujan Es", + "none": "Tidak Ada", + "rain": "Hujan", + "snow": "Salju" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.it.json b/homeassistant/components/tomorrowio/translations/sensor.it.json new file mode 100644 index 00000000000..b8f281a462c --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.it.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Buono", + "hazardous": "Pericoloso", + "moderate": "Moderato", + "unhealthy": "Malsano", + "unhealthy_for_sensitive_groups": "Malsano per i gruppi sensibili", + "very_unhealthy": "Molto malsano" + }, + "tomorrowio__pollen_index": { + "high": "Alto", + "low": "Basso", + "medium": "Medio", + "none": "Nessuno", + "very_high": "Molto alto", + "very_low": "Molto basso" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Pioggia gelata", + "ice_pellets": "Grandine", + "none": "Nessuna", + "rain": "Pioggia", + "snow": "Neve" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.ja.json b/homeassistant/components/tomorrowio/translations/sensor.ja.json new file mode 100644 index 00000000000..286be339435 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.ja.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "\u826f\u3044", + "hazardous": "\u5371\u967a", + "moderate": "\u9069\u5ea6", + "unhealthy": "\u4e0d\u5065\u5eb7", + "unhealthy_for_sensitive_groups": "\u654f\u611f\u306a\u30b0\u30eb\u30fc\u30d7\u306b\u3068\u3063\u3066\u4e0d\u5065\u5eb7", + "very_unhealthy": "\u3068\u3066\u3082\u4e0d\u5065\u5eb7" + }, + "tomorrowio__pollen_index": { + "high": "\u9ad8", + "low": "\u4f4e", + "medium": "\u4e2d", + "none": "\u306a\u3057", + "very_high": "\u975e\u5e38\u306b\u9ad8\u3044", + "very_low": "\u3068\u3066\u3082\u4f4e\u3044" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "\u51cd\u3066\u3064\u304f\u96e8", + "ice_pellets": "\u51cd\u96e8", + "none": "\u306a\u3057", + "rain": "\u96e8", + "snow": "\u96ea" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.nl.json b/homeassistant/components/tomorrowio/translations/sensor.nl.json new file mode 100644 index 00000000000..5ca31721f94 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.nl.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Goed", + "hazardous": "Gevaarlijk", + "moderate": "Gematigd", + "unhealthy": "Ongezond", + "unhealthy_for_sensitive_groups": "Ongezond voor gevoelige groepen", + "very_unhealthy": "Zeer ongezond" + }, + "tomorrowio__pollen_index": { + "high": "Hoog", + "low": "Laag", + "medium": "Medium", + "none": "Geen", + "very_high": "Zeer Hoog", + "very_low": "Zeer Laag" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "IJzel", + "ice_pellets": "Hagel", + "none": "Geen", + "rain": "Regen", + "snow": "Sneeuw" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.no.json b/homeassistant/components/tomorrowio/translations/sensor.no.json new file mode 100644 index 00000000000..2eeabe5b569 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.no.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Bra", + "hazardous": "Farlig", + "moderate": "Moderat", + "unhealthy": "Usunt", + "unhealthy_for_sensitive_groups": "Usunt for sensitive grupper", + "very_unhealthy": "Veldig usunt" + }, + "tomorrowio__pollen_index": { + "high": "H\u00f8y", + "low": "Lav", + "medium": "Medium", + "none": "Ingen", + "very_high": "Veldig h\u00f8y", + "very_low": "Veldig lav" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Underkj\u00f8lt regn", + "ice_pellets": "Is tapper", + "none": "Ingen", + "rain": "Regn", + "snow": "Sn\u00f8" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.pl.json b/homeassistant/components/tomorrowio/translations/sensor.pl.json new file mode 100644 index 00000000000..7140524fc0b --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.pl.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "dobre", + "hazardous": "niebezpieczne", + "moderate": "umiarkowane", + "unhealthy": "niezdrowe", + "unhealthy_for_sensitive_groups": "niezdrowe dla wra\u017cliwych grup", + "very_unhealthy": "bardzo niezdrowe" + }, + "tomorrowio__pollen_index": { + "high": "wysoki", + "low": "niski", + "medium": "\u015bredni", + "none": "brak", + "very_high": "bardzo wysoki", + "very_low": "bardzo niski" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "marzn\u0105cy deszcz", + "ice_pellets": "granulki lodu", + "none": "brak", + "rain": "deszcz", + "snow": "\u015bnieg" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.pt-BR.json b/homeassistant/components/tomorrowio/translations/sensor.pt-BR.json new file mode 100644 index 00000000000..b63572d731d --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.pt-BR.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "Bom", + "hazardous": "Perigoso", + "moderate": "Moderado", + "unhealthy": "Insalubre", + "unhealthy_for_sensitive_groups": "Insalubre para grupos sens\u00edveis", + "very_unhealthy": "Muito Insalubre" + }, + "tomorrowio__pollen_index": { + "high": "Alto", + "low": "Baixo", + "medium": "M\u00e9dio", + "none": "Nenhum", + "very_high": "Muito alto", + "very_low": "Muito baixo" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Chuva congelante", + "ice_pellets": "Pelotas de Gelo", + "none": "Nenhum", + "rain": "Chuva", + "snow": "Neve" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.ru.json b/homeassistant/components/tomorrowio/translations/sensor.ru.json new file mode 100644 index 00000000000..f9be80a4bad --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.ru.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "\u0425\u043e\u0440\u043e\u0448\u043e", + "hazardous": "\u041e\u043f\u0430\u0441\u043d\u043e", + "moderate": "\u0421\u0440\u0435\u0434\u043d\u0435", + "unhealthy": "\u0412\u0440\u0435\u0434\u043d\u043e", + "unhealthy_for_sensitive_groups": "\u0412\u0440\u0435\u0434\u043d\u043e \u0434\u043b\u044f \u0443\u044f\u0437\u0432\u0438\u043c\u044b\u0445 \u0433\u0440\u0443\u043f\u043f", + "very_unhealthy": "\u041e\u0447\u0435\u043d\u044c \u0432\u0440\u0435\u0434\u043d\u043e" + }, + "tomorrowio__pollen_index": { + "high": "\u0412\u044b\u0441\u043e\u043a\u0438\u0439", + "low": "\u041d\u0438\u0437\u043a\u0438\u0439", + "medium": "\u0421\u0440\u0435\u0434\u043d\u0438\u0439", + "none": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442", + "very_high": "\u041e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0438\u0439", + "very_low": "\u041e\u0447\u0435\u043d\u044c \u043d\u0438\u0437\u043a\u0438\u0439" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "\u041b\u0435\u0434\u044f\u043d\u043e\u0439 \u0434\u043e\u0436\u0434\u044c", + "ice_pellets": "\u041b\u0435\u0434\u044f\u043d\u0430\u044f \u043a\u0440\u0443\u043f\u0430", + "none": "\u0411\u0435\u0437 \u043e\u0441\u0430\u0434\u043a\u043e\u0432", + "rain": "\u0414\u043e\u0436\u0434\u044c", + "snow": "\u0421\u043d\u0435\u0433" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.tr.json b/homeassistant/components/tomorrowio/translations/sensor.tr.json new file mode 100644 index 00000000000..553d72f6178 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.tr.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "\u0130yi", + "hazardous": "Tehlikeli", + "moderate": "Il\u0131ml\u0131", + "unhealthy": "Sa\u011fl\u0131ks\u0131z", + "unhealthy_for_sensitive_groups": "Hassas Gruplar \u0130\u00e7in Sa\u011fl\u0131ks\u0131z", + "very_unhealthy": "\u00c7ok Sa\u011fl\u0131ks\u0131z" + }, + "tomorrowio__pollen_index": { + "high": "Y\u00fcksek", + "low": "D\u00fc\u015f\u00fck", + "medium": "Orta", + "none": "Hi\u00e7biri", + "very_high": "\u00c7ok Y\u00fcksek", + "very_low": "\u00c7ok D\u00fc\u015f\u00fck" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "Dondurucu Ya\u011fmur", + "ice_pellets": "Buz Peletleri", + "none": "Hi\u00e7biri", + "rain": "Ya\u011fmur", + "snow": "Kar" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/sensor.zh-Hant.json b/homeassistant/components/tomorrowio/translations/sensor.zh-Hant.json new file mode 100644 index 00000000000..6cc5c1a42dd --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/sensor.zh-Hant.json @@ -0,0 +1,27 @@ +{ + "state": { + "tomorrowio__health_concern": { + "good": "\u826f\u597d", + "hazardous": "\u5371\u96aa", + "moderate": "\u4e2d\u7b49", + "unhealthy": "\u4e0d\u5065\u5eb7", + "unhealthy_for_sensitive_groups": "\u5c0d\u654f\u611f\u65cf\u7fa4\u4e0d\u5065\u5eb7", + "very_unhealthy": "\u975e\u5e38\u4e0d\u5065\u5eb7" + }, + "tomorrowio__pollen_index": { + "high": "\u9ad8", + "low": "\u4f4e", + "medium": "\u4e2d", + "none": "\u7121", + "very_high": "\u6975\u9ad8", + "very_low": "\u6975\u4f4e" + }, + "tomorrowio__precipitation_type": { + "freezing_rain": "\u51cd\u96e8", + "ice_pellets": "\u51b0\u73e0", + "none": "\u7121", + "rain": "\u4e0b\u96e8", + "snow": "\u4e0b\u96ea" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/tr.json b/homeassistant/components/tomorrowio/translations/tr.json new file mode 100644 index 00000000000..4193428459c --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/tr.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", + "rate_limited": "\u015eu anda oran s\u0131n\u0131rl\u0131, l\u00fctfen daha sonra tekrar deneyin.", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "location": "Konum", + "longitude": "Boylam", + "name": "Ad" + }, + "description": "API anahtar\u0131 almak i\u00e7in [Tomorrow.io](https://app.tomorrow.io/signup) adresinden kaydolun." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "NowCast Tahminleri Aras\u0131nda Min." + }, + "description": "\"\u015eimdi yay\u0131n\" tahmin varl\u0131\u011f\u0131n\u0131 etkinle\u015ftirmeyi se\u00e7erseniz, her tahmin aras\u0131ndaki dakika say\u0131s\u0131n\u0131 yap\u0131land\u0131rabilirsiniz. Sa\u011flanan tahminlerin say\u0131s\u0131, tahminler aras\u0131nda se\u00e7ilen dakika say\u0131s\u0131na ba\u011fl\u0131d\u0131r.", + "title": "Tomorrow.io Se\u00e7eneklerini G\u00fcncelleyin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tomorrowio/translations/zh-Hant.json b/homeassistant/components/tomorrowio/translations/zh-Hant.json new file mode 100644 index 00000000000..bf7043b0225 --- /dev/null +++ b/homeassistant/components/tomorrowio/translations/zh-Hant.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_api_key": "API \u91d1\u9470\u7121\u6548", + "rate_limited": "\u9054\u5230\u9650\u5236\u983b\u7387\u3001\u8acb\u7a0d\u5019\u518d\u8a66\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "latitude": "\u7def\u5ea6", + "location": "\u5ea7\u6a19", + "longitude": "\u7d93\u5ea6", + "name": "\u540d\u7a31" + }, + "description": "\u8acb\u53c3\u95b1\u7db2\u5740\u4ee5\u4e86\u89e3\u5982\u4f55\u53d6\u5f97 API \u91d1\u9470\uff1a[Tomorrow.io](https://app.tomorrow.io/signup)\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timestep": "NowCast \u9810\u5831\u9593\u9694\u5206\u9418" + }, + "description": "\u5047\u5982\u9078\u64c7\u958b\u555f `nowcast` \u9810\u5831\u5be6\u9ad4\u3001\u5c07\u53ef\u4ee5\u8a2d\u5b9a\u9810\u5831\u983b\u7387\u9593\u9694\u5206\u9418\u6578\u3002\u6839\u64da\u6240\u8f38\u5165\u7684\u9593\u9694\u6642\u9593\u5c07\u6c7a\u5b9a\u9810\u5831\u7684\u6578\u76ee\u3002", + "title": "\u66f4\u65b0 Tomorrow.io \u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index b5422b7c5fe..07e0aed66ac 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -12,15 +12,13 @@ from toonapi import ( from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_HEAT, PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -44,12 +42,14 @@ async def async_setup_entry( class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): """Representation of a Toon climate device.""" - _attr_hvac_mode = HVAC_MODE_HEAT + _attr_hvac_mode = HVACMode.HEAT _attr_icon = "mdi:thermostat" _attr_max_temp = DEFAULT_MAX_TEMP _attr_min_temp = DEFAULT_MIN_TEMP _attr_name = "Thermostat" - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) _attr_temperature_unit = TEMP_CELSIUS def __init__( @@ -58,7 +58,7 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): ) -> None: """Initialize Toon climate entity.""" super().__init__(coordinator) - self._attr_hvac_modes = [HVAC_MODE_HEAT] + self._attr_hvac_modes = [HVACMode.HEAT] self._attr_preset_modes = [ PRESET_AWAY, PRESET_COMFORT, @@ -70,11 +70,11 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): ) @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction: """Return the current running hvac operation.""" if self.coordinator.data.thermostat.heating: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return HVACAction.HEATING + return HVACAction.IDLE @property def preset_mode(self) -> str | None: @@ -120,7 +120,7 @@ class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): if preset_mode in mapping: await self.coordinator.toon.set_active_state(mapping[preset_mode]) - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" # Intentionally left empty # The HAVC mode is always HEAT diff --git a/homeassistant/components/toon/manifest.json b/homeassistant/components/toon/manifest.json index f6dc4ae2843..3edcae7f357 100644 --- a/homeassistant/components/toon/manifest.json +++ b/homeassistant/components/toon/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/toon", "requirements": ["toonapi==0.2.1"], - "dependencies": ["http"], + "dependencies": ["auth"], "after_dependencies": ["cloud"], "codeowners": [], "dhcp": [ diff --git a/homeassistant/components/toon/translations/hu.json b/homeassistant/components/toon/translations/hu.json index b1a69144dd5..a9192ed013a 100644 --- a/homeassistant/components/toon/translations/hu.json +++ b/homeassistant/components/toon/translations/hu.json @@ -5,7 +5,7 @@ "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", "no_agreements": "Ennek a fi\u00f3knak nincsenek Toon kijelz\u0151i.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3.", "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." }, "step": { diff --git a/homeassistant/components/toon/translations/it.json b/homeassistant/components/toon/translations/it.json index 724c61dba3c..9125bac09db 100644 --- a/homeassistant/components/toon/translations/it.json +++ b/homeassistant/components/toon/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "L'accordo selezionato \u00e8 gi\u00e0 configurato.", "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_agreements": "Questo account non ha display Toon.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "unknown_authorize_url_generation": "Errore sconosciuto durante la generazione di un URL di autorizzazione." diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index 641c0c4477a..8a4aee0debb 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -4,7 +4,11 @@ from datetime import timedelta import logging from total_connect_client.client import TotalConnectClient -from total_connect_client.exceptions import AuthenticationError, TotalConnectError +from total_connect_client.exceptions import ( + AuthenticationError, + ServiceUnavailable, + TotalConnectError, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform @@ -40,7 +44,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: TotalConnectClient, username, password, usercodes ) except AuthenticationError as exception: - raise ConfigEntryAuthFailed("TotalConnect authentication failed") from exception + raise ConfigEntryAuthFailed( + "TotalConnect authentication failed during setup" + ) from exception coordinator = TotalConnectDataUpdateCoordinator(hass, client) await coordinator.async_config_entry_first_refresh() @@ -83,7 +89,12 @@ class TotalConnectDataUpdateCoordinator(DataUpdateCoordinator): except AuthenticationError as exception: # should only encounter if password changes during operation raise ConfigEntryAuthFailed( - "TotalConnect authentication failed" + "TotalConnect authentication failed during operation." + ) from exception + except ServiceUnavailable as exception: + raise UpdateFailed( + "Error connecting to TotalConnect or the service is unavailable. " + "Check https://status.resideo.com/ for outages." ) from exception except TotalConnectError as exception: raise UpdateFailed(exception) from exception diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index d64aab5317b..bf7a1ae410b 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -1,15 +1,9 @@ """Interfaces with TotalConnect alarm control panels.""" -import logging - from total_connect_client import ArmingHelper -from total_connect_client.exceptions import BadResultCodeError +from total_connect_client.exceptions import BadResultCodeError, UsercodeInvalid import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -29,8 +23,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -_LOGGER = logging.getLogger(__name__) - SERVICE_ALARM_ARM_AWAY_INSTANT = "arm_away_instant" SERVICE_ALARM_ARM_HOME_INSTANT = "arm_home_instant" @@ -76,6 +68,12 @@ async def async_setup_entry( class TotalConnectAlarm(CoordinatorEntity, alarm.AlarmControlPanelEntity): """Represent an TotalConnect status.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + ) + def __init__(self, coordinator, name, location_id, partition_id): """Initialize the TotalConnect status.""" super().__init__(coordinator) @@ -160,11 +158,6 @@ class TotalConnectAlarm(CoordinatorEntity, alarm.AlarmControlPanelEntity): return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT - @property def extra_state_attributes(self): """Return the state attributes of the device.""" @@ -172,84 +165,114 @@ class TotalConnectAlarm(CoordinatorEntity, alarm.AlarmControlPanelEntity): async def async_alarm_disarm(self, code=None): """Send disarm command.""" - await self.hass.async_add_executor_job(self._disarm) - await self.coordinator.async_request_refresh() - - def _disarm(self, code=None): - """Disarm synchronous.""" try: - ArmingHelper(self._partition).disarm() + await self.hass.async_add_executor_job(self._disarm) + except UsercodeInvalid as error: + self.coordinator.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError( + "TotalConnect usercode is invalid. Did not disarm" + ) from error except BadResultCodeError as error: raise HomeAssistantError( f"TotalConnect failed to disarm {self._name}." ) from error + await self.coordinator.async_request_refresh() + + def _disarm(self, code=None): + """Disarm synchronous.""" + ArmingHelper(self._partition).disarm() async def async_alarm_arm_home(self, code=None): """Send arm home command.""" - await self.hass.async_add_executor_job(self._arm_home) - await self.coordinator.async_request_refresh() - - def _arm_home(self): - """Arm home synchronous.""" try: - ArmingHelper(self._partition).arm_stay() + await self.hass.async_add_executor_job(self._arm_home) + except UsercodeInvalid as error: + self.coordinator.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError( + "TotalConnect usercode is invalid. Did not arm home" + ) from error except BadResultCodeError as error: raise HomeAssistantError( f"TotalConnect failed to arm home {self._name}." ) from error + await self.coordinator.async_request_refresh() + + def _arm_home(self): + """Arm home synchronous.""" + ArmingHelper(self._partition).arm_stay() async def async_alarm_arm_away(self, code=None): """Send arm away command.""" - await self.hass.async_add_executor_job(self._arm_away) - await self.coordinator.async_request_refresh() - - def _arm_away(self, code=None): - """Arm away synchronous.""" try: - ArmingHelper(self._partition).arm_away() + await self.hass.async_add_executor_job(self._arm_away) + except UsercodeInvalid as error: + self.coordinator.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError( + "TotalConnect usercode is invalid. Did not arm away" + ) from error except BadResultCodeError as error: raise HomeAssistantError( f"TotalConnect failed to arm away {self._name}." ) from error + await self.coordinator.async_request_refresh() + + def _arm_away(self, code=None): + """Arm away synchronous.""" + ArmingHelper(self._partition).arm_away() async def async_alarm_arm_night(self, code=None): """Send arm night command.""" - await self.hass.async_add_executor_job(self._arm_night) - await self.coordinator.async_request_refresh() - - def _arm_night(self, code=None): - """Arm night synchronous.""" try: - ArmingHelper(self._partition).arm_stay_night() + await self.hass.async_add_executor_job(self._arm_night) + except UsercodeInvalid as error: + self.coordinator.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError( + "TotalConnect usercode is invalid. Did not arm night" + ) from error except BadResultCodeError as error: raise HomeAssistantError( f"TotalConnect failed to arm night {self._name}." ) from error + await self.coordinator.async_request_refresh() + + def _arm_night(self, code=None): + """Arm night synchronous.""" + ArmingHelper(self._partition).arm_stay_night() async def async_alarm_arm_home_instant(self, code=None): """Send arm home instant command.""" - await self.hass.async_add_executor_job(self._arm_home_instant) - await self.coordinator.async_request_refresh() - - def _arm_home_instant(self): - """Arm home instant synchronous.""" try: - ArmingHelper(self._partition).arm_stay_instant() + await self.hass.async_add_executor_job(self._arm_home_instant) + except UsercodeInvalid as error: + self.coordinator.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError( + "TotalConnect usercode is invalid. Did not arm home instant" + ) from error except BadResultCodeError as error: raise HomeAssistantError( f"TotalConnect failed to arm home instant {self._name}." ) from error + await self.coordinator.async_request_refresh() + + def _arm_home_instant(self): + """Arm home instant synchronous.""" + ArmingHelper(self._partition).arm_stay_instant() async def async_alarm_arm_away_instant(self, code=None): """Send arm away instant command.""" - await self.hass.async_add_executor_job(self._arm_away_instant) - await self.coordinator.async_request_refresh() - - def _arm_away_instant(self, code=None): - """Arm away instant synchronous.""" try: - ArmingHelper(self._partition).arm_away_instant() + await self.hass.async_add_executor_job(self._arm_away_instant) + except UsercodeInvalid as error: + self.coordinator.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError( + "TotalConnect usercode is invalid. Did not arm away instant" + ) from error except BadResultCodeError as error: raise HomeAssistantError( f"TotalConnect failed to arm away instant {self._name}." ) from error + await self.coordinator.async_request_refresh() + + def _arm_away_instant(self, code=None): + """Arm away instant synchronous.""" + ArmingHelper(self._partition).arm_away_instant() diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json index d2a77080672..bc62a5b17af 100644 --- a/homeassistant/components/totalconnect/manifest.json +++ b/homeassistant/components/totalconnect/manifest.json @@ -2,7 +2,7 @@ "domain": "totalconnect", "name": "Total Connect", "documentation": "https://www.home-assistant.io/integrations/totalconnect", - "requirements": ["total_connect_client==2022.2.1"], + "requirements": ["total_connect_client==2022.3"], "dependencies": [], "codeowners": ["@austinmroczek"], "config_flow": true, diff --git a/homeassistant/components/touchline/climate.py b/homeassistant/components/touchline/climate.py index f881c7a704a..584f23b1ded 100644 --- a/homeassistant/components/touchline/climate.py +++ b/homeassistant/components/touchline/climate.py @@ -7,11 +7,7 @@ from pytouchline import PyTouchline import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, TEMP_CELSIUS from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -40,8 +36,6 @@ TOUCHLINE_HA_PRESETS = { for preset, settings in PRESET_MODES.items() } -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_HOST): cv.string}) @@ -65,6 +59,12 @@ def setup_platform( class Touchline(ClimateEntity): """Representation of a Touchline device.""" + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, touchline_thermostat): """Initialize the Touchline device.""" self.unit = touchline_thermostat @@ -74,11 +74,6 @@ class Touchline(ClimateEntity): self._current_operation_mode = None self._preset_mode = None - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS - def update(self): """Update thermostat attributes.""" self.unit.update() @@ -89,19 +84,6 @@ class Touchline(ClimateEntity): (self.unit.get_operation_mode(), self.unit.get_week_program()) ) - @property - def hvac_mode(self): - """Return current HVAC mode. - - Need to be one of HVAC_MODE_*. - """ - return HVAC_MODE_HEAT - - @property - def hvac_modes(self): - """Return list of possible operation modes.""" - return [HVAC_MODE_HEAT] - @property def should_poll(self): """Return the polling state.""" @@ -143,9 +125,9 @@ class Touchline(ClimateEntity): self.unit.set_operation_mode(preset_mode.mode) self.unit.set_week_program(preset_mode.program) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - self._current_operation_mode = HVAC_MODE_HEAT + self._current_operation_mode = HVACMode.HEAT def set_temperature(self, **kwargs): """Set new target temperature.""" diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index ecbcb84d772..f946629813e 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -14,13 +14,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, ATTR_TRANSITION, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -166,6 +162,8 @@ async def async_setup_entry( class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity): """Representation of a TPLink Smart Bulb.""" + _attr_supported_features = LightEntityFeature.TRANSITION + device: SmartBulb def __init__( @@ -280,37 +278,32 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity): return hue, saturation @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_TRANSITION - - @property - def supported_color_modes(self) -> set[str] | None: + def supported_color_modes(self) -> set[ColorMode | str] | None: """Return list of available color modes.""" - modes = set() + modes: set[ColorMode | str] = set() if self.device.is_variable_color_temp: - modes.add(COLOR_MODE_COLOR_TEMP) + modes.add(ColorMode.COLOR_TEMP) if self.device.is_color: - modes.add(COLOR_MODE_HS) + modes.add(ColorMode.HS) if self.device.is_dimmable: - modes.add(COLOR_MODE_BRIGHTNESS) + modes.add(ColorMode.BRIGHTNESS) if not modes: - modes.add(COLOR_MODE_ONOFF) + modes.add(ColorMode.ONOFF) return modes @property - def color_mode(self) -> str | None: + def color_mode(self) -> ColorMode: """Return the active color mode.""" if self.device.is_color: if self.device.is_variable_color_temp and self.device.color_temp: - return COLOR_MODE_COLOR_TEMP - return COLOR_MODE_HS + return ColorMode.COLOR_TEMP + return ColorMode.HS if self.device.is_variable_color_temp: - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP - return COLOR_MODE_BRIGHTNESS + return ColorMode.BRIGHTNESS class TPLinkSmartLightStrip(TPLinkSmartBulb): @@ -321,7 +314,7 @@ class TPLinkSmartLightStrip(TPLinkSmartBulb): @property def supported_features(self) -> int: """Flag supported features.""" - return super().supported_features | SUPPORT_EFFECT + return super().supported_features | LightEntityFeature.EFFECT @property def effect_list(self) -> list[str] | None: diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index 3b23f466296..9a419302a18 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -3,13 +3,65 @@ "name": "TP-Link Kasa Smart", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tplink", - "requirements": ["python-kasa==0.4.3"], - "codeowners": ["@rytilahti", "@thegardenmonkey"], + "requirements": ["python-kasa==0.5.0"], + "codeowners": ["@rytilahti", "@thegardenmonkey", "@bdraco"], "dependencies": ["network"], "quality_scale": "platinum", "iot_class": "local_polling", "dhcp": [ { "registered_devices": true }, + { + "hostname": "ep*", + "macaddress": "E848B8*" + }, + { + "hostname": "ep*", + "macaddress": "003192*" + }, + { + "hostname": "hs*", + "macaddress": "1C3BF3*" + }, + { + "hostname": "hs*", + "macaddress": "50C7BF*" + }, + { + "hostname": "hs*", + "macaddress": "68FF7B*" + }, + { + "hostname": "hs*", + "macaddress": "98DAC4*" + }, + { + "hostname": "hs*", + "macaddress": "B09575*" + }, + { + "hostname": "hs*", + "macaddress": "C006C3*" + }, + { + "hostname": "lb*", + "macaddress": "1C3BF3*" + }, + { + "hostname": "lb*", + "macaddress": "50C7BF*" + }, + { + "hostname": "lb*", + "macaddress": "68FF7B*" + }, + { + "hostname": "lb*", + "macaddress": "98DAC4*" + }, + { + "hostname": "lb*", + "macaddress": "B09575*" + }, { "hostname": "k[lp]*", "macaddress": "60A4B7*" @@ -22,6 +74,10 @@ "hostname": "k[lp]*", "macaddress": "1027F5*" }, + { + "hostname": "k[lp]*", + "macaddress": "B0A7B9*" + }, { "hostname": "k[lp]*", "macaddress": "403F8C*" @@ -30,45 +86,13 @@ "hostname": "k[lp]*", "macaddress": "C0C9E3*" }, - { - "hostname": "ep*", - "macaddress": "E848B8*" - }, - { - "hostname": "k[lp]*", - "macaddress": "E848B8*" - }, { "hostname": "k[lp]*", "macaddress": "909A4A*" }, { - "hostname": "hs*", - "macaddress": "1C3BF3*" - }, - { - "hostname": "hs*", - "macaddress": "50C7BF*" - }, - { - "hostname": "hs*", - "macaddress": "68FF7B*" - }, - { - "hostname": "hs*", - "macaddress": "98DAC4*" - }, - { - "hostname": "hs*", - "macaddress": "B09575*" - }, - { - "hostname": "hs*", - "macaddress": "C006C3*" - }, - { - "hostname": "ep*", - "macaddress": "003192*" + "hostname": "k[lp]*", + "macaddress": "E848B8*" }, { "hostname": "k[lp]*", @@ -99,24 +123,8 @@ "macaddress": "C006C3*" }, { - "hostname": "lb*", - "macaddress": "1C3BF3*" - }, - { - "hostname": "lb*", - "macaddress": "50C7BF*" - }, - { - "hostname": "lb*", - "macaddress": "68FF7B*" - }, - { - "hostname": "lb*", - "macaddress": "98DAC4*" - }, - { - "hostname": "lb*", - "macaddress": "B09575*" + "hostname": "k[lp]*", + "macaddress": "6C5AB0*" } ], "loggers": ["kasa"] diff --git a/homeassistant/components/tplink/translations/it.json b/homeassistant/components/tplink/translations/it.json index 3fd30d8d12c..6ab46615373 100644 --- a/homeassistant/components/tplink/translations/it.json +++ b/homeassistant/components/tplink/translations/it.json @@ -25,7 +25,7 @@ "data": { "host": "Host" }, - "description": "Se si lascia vuoto l'host, l'individuazione verr\u00e0 utilizzata per trovare i dispositivi." + "description": "Se si lascia vuoto l'host, l'individuazione sar\u00e0 utilizzata per trovare i dispositivi." } } } diff --git a/homeassistant/components/tractive/__init__.py b/homeassistant/components/tractive/__init__.py index 99b47559d80..ff2ba9c34bf 100644 --- a/homeassistant/components/tractive/__init__.py +++ b/homeassistant/components/tractive/__init__.py @@ -30,6 +30,7 @@ from .const import ( ATTR_MINUTES_ACTIVE, ATTR_TRACKER_STATE, CLIENT, + CLIENT_ID, DOMAIN, RECONNECT_INTERVAL, SERVER_UNAVAILABLE, @@ -68,7 +69,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {}) client = aiotractive.Tractive( - data[CONF_EMAIL], data[CONF_PASSWORD], session=async_get_clientsession(hass) + data[CONF_EMAIL], + data[CONF_PASSWORD], + session=async_get_clientsession(hass), + client_id=CLIENT_ID, ) try: creds = await client.authenticate() diff --git a/homeassistant/components/tractive/const.py b/homeassistant/components/tractive/const.py index 0d7d62ccae7..a87e22c505d 100644 --- a/homeassistant/components/tractive/const.py +++ b/homeassistant/components/tractive/const.py @@ -13,6 +13,10 @@ ATTR_LIVE_TRACKING = "live_tracking" ATTR_MINUTES_ACTIVE = "minutes_active" ATTR_TRACKER_STATE = "tracker_state" +# This client ID was issued by Tractive specifically for Home Assistant. +# Please do not use it anywhere else. +CLIENT_ID = "625e5349c3c3b41c28a669f1" + CLIENT = "client" TRACKABLES = "trackables" diff --git a/homeassistant/components/tractive/diagnostics.py b/homeassistant/components/tractive/diagnostics.py new file mode 100644 index 00000000000..879e9d82e7b --- /dev/null +++ b/homeassistant/components/tractive/diagnostics.py @@ -0,0 +1,28 @@ +"""Diagnostics support for Tractive.""" +from __future__ import annotations + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.core import HomeAssistant + +from .const import DOMAIN, TRACKABLES + +TO_REDACT = {CONF_PASSWORD, CONF_EMAIL, "title", "_id"} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict: + """Return diagnostics for a config entry.""" + trackables = hass.data[DOMAIN][config_entry.entry_id][TRACKABLES] + + diagnostics_data = async_redact_data( + { + "config_entry": config_entry.as_dict(), + "trackables": [item.trackable for item in trackables], + }, + TO_REDACT, + ) + + return diagnostics_data diff --git a/homeassistant/components/tractive/manifest.json b/homeassistant/components/tractive/manifest.json index be005e3104a..a672c862959 100644 --- a/homeassistant/components/tractive/manifest.json +++ b/homeassistant/components/tractive/manifest.json @@ -3,7 +3,7 @@ "name": "Tractive", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tractive", - "requirements": ["aiotractive==0.5.2"], + "requirements": ["aiotractive==0.5.4"], "codeowners": ["@Danielhiversen", "@zhulik", "@bieniu"], "iot_class": "cloud_push", "loggers": ["aiotractive"] diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 0054b1d7bff..e4d13568e6d 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -2,19 +2,15 @@ from __future__ import annotations from datetime import datetime, timedelta -import logging from typing import Any from pytradfri import Gateway, RequestError from pytradfri.api.aiocoap_api import APIFactory from pytradfri.command import Command from pytradfri.device import Device -from pytradfri.group import Group -import voluptuous as vol -from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv @@ -24,82 +20,30 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import ConfigType from .const import ( - ATTR_TRADFRI_GATEWAY, - ATTR_TRADFRI_GATEWAY_MODEL, - ATTR_TRADFRI_MANUFACTURER, - CONF_ALLOW_TRADFRI_GROUPS, CONF_GATEWAY_ID, CONF_IDENTITY, - CONF_IMPORT_GROUPS, CONF_KEY, COORDINATOR, COORDINATOR_LIST, - DEFAULT_ALLOW_TRADFRI_GROUPS, DOMAIN, - GROUPS_LIST, + FACTORY, KEY_API, - PLATFORMS, - SIGNAL_GW, - TIMEOUT_API, -) -from .coordinator import ( - TradfriDeviceDataUpdateCoordinator, - TradfriGroupDataUpdateCoordinator, + LOGGER, ) +from .coordinator import TradfriDeviceDataUpdateCoordinator -_LOGGER = logging.getLogger(__name__) - -FACTORY = "tradfri_factory" -LISTENERS = "tradfri_listeners" - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - vol.All( - cv.deprecated(CONF_HOST), - cv.deprecated( - CONF_ALLOW_TRADFRI_GROUPS, - ), - { - vol.Optional(CONF_HOST): cv.string, - vol.Optional( - CONF_ALLOW_TRADFRI_GROUPS, default=DEFAULT_ALLOW_TRADFRI_GROUPS - ): cv.boolean, - }, - ), - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Tradfri component.""" - if (conf := config.get(DOMAIN)) is None: - return True - - configured_hosts = [ - entry.data.get("host") for entry in hass.config_entries.async_entries(DOMAIN) - ] - - host = conf.get(CONF_HOST) - import_groups = conf[CONF_ALLOW_TRADFRI_GROUPS] - - if host is None or host in configured_hosts: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_HOST: host, CONF_IMPORT_GROUPS: import_groups}, - ) - ) - - return True +CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) +PLATFORMS = [ + Platform.COVER, + Platform.FAN, + Platform.LIGHT, + Platform.SENSOR, + Platform.SWITCH, +] +SIGNAL_GW = "tradfri.gw_status" +TIMEOUT_API = 30 async def async_setup_entry( @@ -109,7 +53,6 @@ async def async_setup_entry( """Create a gateway.""" tradfri_data: dict[str, Any] = {} hass.data.setdefault(DOMAIN, {})[entry.entry_id] = tradfri_data - listeners = tradfri_data[LISTENERS] = [] factory = await APIFactory.init( entry.data[CONF_HOST], @@ -123,11 +66,12 @@ async def async_setup_entry( await factory.shutdown() # Setup listeners - listeners.append(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)) + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) + ) api = factory.request gateway = Gateway() - groups: list[Group] = [] try: gateway_info = await api(gateway.get_gateway_info(), timeout=TIMEOUT_API) @@ -136,19 +80,6 @@ async def async_setup_entry( ) devices: list[Device] = await api(devices_commands, timeout=TIMEOUT_API) - if entry.data[CONF_IMPORT_GROUPS]: - # Note: we should update this page when deprecating: - # https://www.home-assistant.io/integrations/tradfri/ - _LOGGER.warning( - "Importing of Tradfri groups has been deprecated due to stability issues " - "and will be removed in Home Assistant core 2022.5" - ) - # No need to load groups if the user hasn't requested it - groups_commands: Command = await api( - gateway.get_groups(), timeout=TIMEOUT_API - ) - groups = await api(groups_commands, timeout=TIMEOUT_API) - except RequestError as exc: await factory.shutdown() raise ConfigEntryNotReady from exc @@ -158,10 +89,10 @@ async def async_setup_entry( config_entry_id=entry.entry_id, connections=set(), identifiers={(DOMAIN, entry.data[CONF_GATEWAY_ID])}, - manufacturer=ATTR_TRADFRI_MANUFACTURER, - name=ATTR_TRADFRI_GATEWAY, + manufacturer="IKEA of Sweden", + name="Gateway", # They just have 1 gateway model. Type is not exposed yet. - model=ATTR_TRADFRI_GATEWAY_MODEL, + model="E1526", sw_version=gateway_info.firmware_version, ) @@ -172,7 +103,6 @@ async def async_setup_entry( CONF_GATEWAY_ID: gateway, KEY_API: api, COORDINATOR_LIST: [], - GROUPS_LIST: [], } for device in devices: @@ -186,18 +116,6 @@ async def async_setup_entry( ) coordinator_data[COORDINATOR_LIST].append(coordinator) - for group in groups: - group_coordinator = TradfriGroupDataUpdateCoordinator( - hass=hass, api=api, group=group - ) - await group_coordinator.async_config_entry_first_refresh() - entry.async_on_unload( - async_dispatcher_connect( - hass, SIGNAL_GW, group_coordinator.set_hub_available - ) - ) - coordinator_data[GROUPS_LIST].append(group_coordinator) - tradfri_data[COORDINATOR] = coordinator_data async def async_keep_alive(now: datetime) -> None: @@ -208,12 +126,12 @@ async def async_setup_entry( try: await api(gateway.get_gateway_info()) except RequestError: - _LOGGER.error("Keep-alive failed") + LOGGER.error("Keep-alive failed") gw_status = False async_dispatcher_send(hass, SIGNAL_GW, gw_status) - listeners.append( + entry.async_on_unload( async_track_time_interval(hass, async_keep_alive, timedelta(seconds=60)) ) @@ -229,9 +147,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: tradfri_data = hass.data[DOMAIN].pop(entry.entry_id) factory = tradfri_data[FACTORY] await factory.shutdown() - # unsubscribe listeners - for listener in tradfri_data[LISTENERS]: - listener() return unload_ok diff --git a/homeassistant/components/tradfri/base_class.py b/homeassistant/components/tradfri/base_class.py index 727e611dca4..bd1968dfd15 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -4,7 +4,6 @@ from __future__ import annotations from abc import abstractmethod from collections.abc import Callable from functools import wraps -import logging from typing import Any, cast from pytradfri.command import Command @@ -15,11 +14,9 @@ from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import DOMAIN, LOGGER from .coordinator import TradfriDeviceDataUpdateCoordinator -_LOGGER = logging.getLogger(__name__) - def handle_error( func: Callable[[Command | list[Command]], Any] @@ -32,7 +29,7 @@ def handle_error( try: await func(command) except RequestError as err: - _LOGGER.error("Unable to execute command %s: %s", command, err) + LOGGER.error("Unable to execute command %s: %s", command, err) return wrapper diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index d39eef864be..20dc2ed9510 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -16,14 +16,9 @@ from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult -from .const import ( - CONF_GATEWAY_ID, - CONF_IDENTITY, - CONF_IMPORT_GROUPS, - CONF_KEY, - DOMAIN, - KEY_SECURITY_CODE, -) +from .const import CONF_GATEWAY_ID, CONF_IDENTITY, CONF_KEY, DOMAIN + +KEY_SECURITY_CODE = "security_code" class AuthError(Exception): @@ -43,7 +38,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize flow.""" self._host: str | None = None - self._import_groups = False async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -64,11 +58,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.hass, host, user_input[KEY_SECURITY_CODE] ) - # We don't ask for import group anymore as group state - # is not reliable, don't want to show that to the user. - # But we still allow specifying import group via config yaml. - auth[CONF_IMPORT_GROUPS] = self._import_groups - return await self._entry_from_data(auth) except AuthError as err: @@ -126,7 +115,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Happens if user has host directly in configuration.yaml if "key" not in user_input: self._host = user_input["host"] - self._import_groups = user_input[CONF_IMPORT_GROUPS] return await self.async_step_auth() try: @@ -138,8 +126,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): user_input["key"], ) - data[CONF_IMPORT_GROUPS] = user_input[CONF_IMPORT_GROUPS] - return await self._entry_from_data(data) except AuthError: # If we fail to connect, just pass it on to discovery diff --git a/homeassistant/components/tradfri/const.py b/homeassistant/components/tradfri/const.py index 3d68ebbaee0..23b97efedd5 100644 --- a/homeassistant/components/tradfri/const.py +++ b/homeassistant/components/tradfri/const.py @@ -1,49 +1,13 @@ """Consts used by Tradfri.""" -from typing import Final +import logging -from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION -from homeassistant.const import ( # noqa: F401 pylint: disable=unused-import - CONF_HOST, - Platform, -) +LOGGER = logging.getLogger(__package__) -ATTR_AUTO = "Auto" -ATTR_DIMMER = "dimmer" -ATTR_HUE = "hue" -ATTR_SAT = "saturation" -ATTR_TRADFRI_GATEWAY = "Gateway" -ATTR_TRADFRI_GATEWAY_MODEL = "E1526" -ATTR_TRADFRI_MANUFACTURER = "IKEA of Sweden" -ATTR_TRANSITION_TIME = "transition_time" -ATTR_MODEL = "model" -CONF_ALLOW_TRADFRI_GROUPS = "allow_tradfri_groups" -CONF_IDENTITY = "identity" -CONF_IMPORT_GROUPS = "import_groups" CONF_GATEWAY_ID = "gateway_id" +CONF_IDENTITY = "identity" CONF_KEY = "key" -DEFAULT_ALLOW_TRADFRI_GROUPS = False -DOMAIN = "tradfri" -KEY_API = "tradfri_api" -DEVICES = "tradfri_devices" -GROUPS = "tradfri_groups" -SIGNAL_GW = "tradfri.gw_status" -KEY_SECURITY_CODE = "security_code" -SUPPORTED_GROUP_FEATURES = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION -SUPPORTED_LIGHT_FEATURES = SUPPORT_TRANSITION -PLATFORMS = [ - Platform.COVER, - Platform.FAN, - Platform.LIGHT, - Platform.SENSOR, - Platform.SWITCH, -] -TIMEOUT_API = 30 -ATTR_MAX_FAN_STEPS = 49 - -SCAN_INTERVAL = 60 # Interval for updating the coordinator - COORDINATOR = "coordinator" COORDINATOR_LIST = "coordinator_list" -GROUPS_LIST = "groups_list" - -ATTR_FILTER_LIFE_REMAINING: Final = "filter_life_remaining" +DOMAIN = "tradfri" +FACTORY = "tradfri_factory" +KEY_API = "tradfri_api" diff --git a/homeassistant/components/tradfri/coordinator.py b/homeassistant/components/tradfri/coordinator.py index 5a516e8f46e..0d90b4ee28c 100644 --- a/homeassistant/components/tradfri/coordinator.py +++ b/homeassistant/components/tradfri/coordinator.py @@ -3,20 +3,18 @@ from __future__ import annotations from collections.abc import Callable from datetime import timedelta -import logging from typing import Any from pytradfri.command import Command from pytradfri.device import Device from pytradfri.error import RequestError -from pytradfri.group import Group from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import SCAN_INTERVAL +from .const import LOGGER -_LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = 60 # Interval for updating the coordinator class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): @@ -36,7 +34,7 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): super().__init__( hass, - _LOGGER, + LOGGER, name=f"Update coordinator for {device}", update_interval=timedelta(seconds=SCAN_INTERVAL), ) @@ -63,7 +61,7 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): # Store exception so that it gets raised in _async_update_data self._exception = exc - _LOGGER.debug( + LOGGER.debug( "Observation failed for %s, trying again", self.device, exc_info=exc ) # Change interval so we get a swift refresh @@ -95,47 +93,3 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): self.update_interval = timedelta(seconds=SCAN_INTERVAL) return self.device - - -class TradfriGroupDataUpdateCoordinator(DataUpdateCoordinator[Group]): - """Coordinator to manage data for a specific Tradfri group.""" - - def __init__( - self, - hass: HomeAssistant, - *, - api: Callable[[Command | list[Command]], Any], - group: Group, - ) -> None: - """Initialize group coordinator.""" - self.api = api - self.group = group - self._exception: Exception | None = None - - super().__init__( - hass, - _LOGGER, - name=f"Update coordinator for {group}", - update_interval=timedelta(seconds=SCAN_INTERVAL), - ) - - async def set_hub_available(self, available: bool) -> None: - """Set status of hub.""" - if available != self.last_update_success: - if not available: - self.last_update_success = False - await self.async_request_refresh() - - async def _async_update_data(self) -> Group: - """Fetch data from the gateway for a specific group.""" - self.update_interval = timedelta(seconds=SCAN_INTERVAL) # Reset update interval - cmd = self.group.update() - try: - await self.api(cmd) - except RequestError as exc: - self.update_interval = timedelta( - seconds=5 - ) # Change interval so we get a swift refresh - raise UpdateFailed("Unable to update group coordinator") from exc - - return self.group diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index d4c3063f35d..976a48906fc 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -12,14 +12,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .base_class import TradfriBaseEntity -from .const import ( - ATTR_MODEL, - CONF_GATEWAY_ID, - COORDINATOR, - COORDINATOR_LIST, - DOMAIN, - KEY_API, -) +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API from .coordinator import TradfriDeviceDataUpdateCoordinator @@ -70,7 +63,7 @@ class TradfriCover(TradfriBaseEntity, CoverEntity): @property def extra_state_attributes(self) -> dict[str, str] | None: """Return the state attributes.""" - return {ATTR_MODEL: self._device.device_info.model_number} + return {"model": self._device.device_info.model_number} @property def current_cover_position(self) -> int | None: diff --git a/homeassistant/components/tradfri/diagnostics.py b/homeassistant/components/tradfri/diagnostics.py index 81f20c4a46a..c415632faa4 100644 --- a/homeassistant/components/tradfri/diagnostics.py +++ b/homeassistant/components/tradfri/diagnostics.py @@ -7,7 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, GROUPS_LIST +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN async def async_get_config_entry_diagnostics( @@ -32,5 +32,4 @@ async def async_get_config_entry_diagnostics( return { "gateway_version": device.sw_version, "device_data": sorted(device_data), - "no_of_groups": len(coordinator_data[GROUPS_LIST]), } diff --git a/homeassistant/components/tradfri/fan.py b/homeassistant/components/tradfri/fan.py index ed478025405..0924da11259 100644 --- a/homeassistant/components/tradfri/fan.py +++ b/homeassistant/components/tradfri/fan.py @@ -7,27 +7,18 @@ from typing import Any, cast from pytradfri.command import Command -from homeassistant.components.fan import ( - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .base_class import TradfriBaseEntity -from .const import ( - ATTR_AUTO, - ATTR_MAX_FAN_STEPS, - CONF_GATEWAY_ID, - COORDINATOR, - COORDINATOR_LIST, - DOMAIN, - KEY_API, -) +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API from .coordinator import TradfriDeviceDataUpdateCoordinator +ATTR_AUTO = "Auto" +ATTR_MAX_FAN_STEPS = 49 + def _from_fan_percentage(percentage: int) -> int: """Convert percent to a value that the Tradfri API understands.""" @@ -63,6 +54,8 @@ async def async_setup_entry( class TradfriAirPurifierFan(TradfriBaseEntity, FanEntity): """The platform class required by Home Assistant.""" + _attr_supported_features = FanEntityFeature.PRESET_MODE | FanEntityFeature.SET_SPEED + def __init__( self, device_coordinator: TradfriDeviceDataUpdateCoordinator, @@ -83,11 +76,6 @@ class TradfriAirPurifierFan(TradfriBaseEntity, FanEntity): """Refresh the device.""" self._device_data = self.coordinator.data.air_purifier_control.air_purifiers[0] - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_PRESET_MODE + SUPPORT_SET_SPEED - @property def speed_count(self) -> int: """ diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index bae626017e7..32160c6a130 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -5,44 +5,24 @@ from collections.abc import Callable from typing import Any, cast from pytradfri.command import Command -from pytradfri.group import Group from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity import homeassistant.util.color as color_util from .base_class import TradfriBaseEntity -from .const import ( - ATTR_DIMMER, - ATTR_HUE, - ATTR_SAT, - ATTR_TRANSITION_TIME, - CONF_GATEWAY_ID, - CONF_IMPORT_GROUPS, - COORDINATOR, - COORDINATOR_LIST, - DOMAIN, - GROUPS_LIST, - KEY_API, - SUPPORTED_GROUP_FEATURES, - SUPPORTED_LIGHT_FEATURES, -) -from .coordinator import ( - TradfriDeviceDataUpdateCoordinator, - TradfriGroupDataUpdateCoordinator, -) +from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API +from .coordinator import TradfriDeviceDataUpdateCoordinator async def async_setup_entry( @@ -55,7 +35,7 @@ async def async_setup_entry( coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] api = coordinator_data[KEY_API] - entities: list = [ + async_add_entities( TradfriLight( device_coordinator, api, @@ -63,76 +43,14 @@ async def async_setup_entry( ) for device_coordinator in coordinator_data[COORDINATOR_LIST] if device_coordinator.device.has_light_control - ] - - if config_entry.data[CONF_IMPORT_GROUPS] and ( - group_coordinators := coordinator_data[GROUPS_LIST] - ): - entities.extend( - [ - TradfriGroup(group_coordinator, api, gateway_id) - for group_coordinator in group_coordinators - ] - ) - - async_add_entities(entities) - - -class TradfriGroup(CoordinatorEntity[TradfriGroupDataUpdateCoordinator], LightEntity): - """The platform class for light groups required by hass.""" - - _attr_supported_features = SUPPORTED_GROUP_FEATURES - - def __init__( - self, - group_coordinator: TradfriGroupDataUpdateCoordinator, - api: Callable[[Command | list[Command]], Any], - gateway_id: str, - ) -> None: - """Initialize a Group.""" - super().__init__(coordinator=group_coordinator) - - self._group: Group = self.coordinator.data - - self._api = api - self._attr_unique_id = f"group-{gateway_id}-{self._group.id}" - - @property - def is_on(self) -> bool: - """Return true if group lights are on.""" - return cast(bool, self._group.state) - - @property - def brightness(self) -> int | None: - """Return the brightness of the group lights.""" - return cast(int, self._group.dimmer) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Instruct the group lights to turn off.""" - await self._api(self._group.set_state(0)) - - await self.coordinator.async_request_refresh() - - async def async_turn_on(self, **kwargs: Any) -> None: - """Instruct the group lights to turn on, or dim.""" - keys = {} - if ATTR_TRANSITION in kwargs: - keys["transition_time"] = int(kwargs[ATTR_TRANSITION]) * 10 - - if ATTR_BRIGHTNESS in kwargs: - if kwargs[ATTR_BRIGHTNESS] == 255: - kwargs[ATTR_BRIGHTNESS] = 254 - - await self._api(self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys)) - else: - await self._api(self._group.set_state(1)) - - await self.coordinator.async_request_refresh() + ) class TradfriLight(TradfriBaseEntity, LightEntity): """The platform class required by Home Assistant.""" + _attr_supported_features = LightEntityFeature.TRANSITION + def __init__( self, device_coordinator: TradfriDeviceDataUpdateCoordinator, @@ -152,15 +70,19 @@ class TradfriLight(TradfriBaseEntity, LightEntity): self._attr_unique_id = f"light-{gateway_id}-{self._device_id}" self._hs_color = None - # Calculate supported features - _features = SUPPORTED_LIGHT_FEATURES - if self._device.light_control.can_set_dimmer: - _features |= SUPPORT_BRIGHTNESS + # Calculate supported color modes + self._attr_supported_color_modes: set[ColorMode] = set() if self._device.light_control.can_set_color: - _features |= SUPPORT_COLOR | SUPPORT_COLOR_TEMP + self._attr_supported_color_modes.add(ColorMode.HS) if self._device.light_control.can_set_temp: - _features |= SUPPORT_COLOR_TEMP - self._attr_supported_features = _features + self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) + if ( + not self._attr_supported_color_modes + and self._device.light_control.can_set_dimmer + ): + # Must be the only supported mode according to docs for + # ColorMode.BRIGHTNESS + self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) if self._device_control: self._attr_min_mireds = self._device_control.min_mireds @@ -214,8 +136,11 @@ class TradfriLight(TradfriBaseEntity, LightEntity): if ATTR_TRANSITION in kwargs: transition_time = int(kwargs[ATTR_TRANSITION]) * 10 - dimmer_data = {ATTR_DIMMER: 0, ATTR_TRANSITION_TIME: transition_time} - await self._api(self._device_control.set_dimmer(**dimmer_data)) + await self._api( + self._device_control.set_dimmer( + dimmer=0, transition_time=transition_time + ) + ) else: await self._api(self._device_control.set_state(False)) @@ -232,8 +157,8 @@ class TradfriLight(TradfriBaseEntity, LightEntity): brightness = kwargs[ATTR_BRIGHTNESS] brightness = min(brightness, 254) dimmer_data = { - ATTR_DIMMER: brightness, - ATTR_TRANSITION_TIME: transition_time, + "dimmer": brightness, + "transition_time": transition_time, } dimmer_command = self._device_control.set_dimmer(**dimmer_data) transition_time = None @@ -247,9 +172,9 @@ class TradfriLight(TradfriBaseEntity, LightEntity): kwargs[ATTR_HS_COLOR][1] * (self._device_control.max_saturation / 100) ) color_data = { - ATTR_HUE: hue, - ATTR_SAT: sat, - ATTR_TRANSITION_TIME: transition_time, + "hue": hue, + "saturation": sat, + "transition_time": transition_time, } color_command = self._device_control.set_hsb(**color_data) transition_time = None @@ -267,7 +192,7 @@ class TradfriLight(TradfriBaseEntity, LightEntity): temp = self.min_mireds temp_data = { ATTR_COLOR_TEMP: temp, - ATTR_TRANSITION_TIME: transition_time, + "transition_time": transition_time, } temp_command = self._device_control.set_color_temp(**temp_data) transition_time = None @@ -279,9 +204,9 @@ class TradfriLight(TradfriBaseEntity, LightEntity): hue = int(hs_color[0] * (self._device_control.max_hue / 360)) sat = int(hs_color[1] * (self._device_control.max_saturation / 100)) color_data = { - ATTR_HUE: hue, - ATTR_SAT: sat, - ATTR_TRANSITION_TIME: transition_time, + "hue": hue, + "saturation": sat, + "transition_time": transition_time, } color_command = self._device_control.set_hsb(**color_data) transition_time = None diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 1654b780124..ebd90333292 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -import logging from typing import Any, cast from pytradfri.command import Command @@ -28,17 +27,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .base_class import TradfriBaseEntity from .const import ( - ATTR_FILTER_LIFE_REMAINING, CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API, + LOGGER, ) from .coordinator import TradfriDeviceDataUpdateCoordinator -_LOGGER = logging.getLogger(__name__) - @dataclass class TradfriSensorEntityDescriptionMixin: @@ -91,7 +88,7 @@ SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = ( value=_get_air_quality, ), TradfriSensorEntityDescription( - key=ATTR_FILTER_LIFE_REMAINING, + key="filter_life_remaining", name="filter time left", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=TIME_HOURS, @@ -116,14 +113,14 @@ def _migrate_old_unique_ids(hass: HomeAssistant, old_unique_id: str, key: str) - try: ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) except ValueError: - _LOGGER.warning( + LOGGER.warning( "Skip migration of id [%s] to [%s] because it already exists", old_unique_id, new_unique_id, ) return - _LOGGER.debug( + LOGGER.debug( "Migrating unique_id from [%s] to [%s]", old_unique_id, new_unique_id, diff --git a/homeassistant/components/tradfri/translations/hu.json b/homeassistant/components/tradfri/translations/hu.json index 01bb51b0af3..03a8ddd8fa6 100644 --- a/homeassistant/components/tradfri/translations/hu.json +++ b/homeassistant/components/tradfri/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van" + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve" }, "error": { "cannot_authenticate": "Sikertelen hiteles\u00edt\u00e9s. A Gateway egy m\u00e1sik eszk\u00f6zzel van p\u00e1ros\u00edtva, mint p\u00e9ld\u00e1ul a Homekittel?", diff --git a/homeassistant/components/trafikverket_ferry/__init__.py b/homeassistant/components/trafikverket_ferry/__init__.py new file mode 100644 index 00000000000..5042e7c5167 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/__init__.py @@ -0,0 +1,26 @@ +"""The trafikverket_ferry component.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN, PLATFORMS +from .coordinator import TVDataUpdateCoordinator + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Trafikverket Ferry from a config entry.""" + + coordinator = TVDataUpdateCoordinator(hass, entry) + await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Trafikverket Ferry config entry.""" + + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/trafikverket_ferry/config_flow.py b/homeassistant/components/trafikverket_ferry/config_flow.py new file mode 100644 index 00000000000..7f9737cf686 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/config_flow.py @@ -0,0 +1,162 @@ +"""Adds config flow for Trafikverket Ferry integration.""" +from __future__ import annotations + +from typing import Any + +from pytrafikverket import TrafikverketFerry +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, WEEKDAYS +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import selector +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN +from .util import create_unique_id + +ERROR_INVALID_AUTH = "Source: Security, message: Invalid authentication" +ERROR_INVALID_ROUTE = "No FerryAnnouncement found" + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): selector.TextSelector( + selector.TextSelectorConfig() + ), + vol.Required(CONF_FROM): selector.TextSelector(selector.TextSelectorConfig()), + vol.Optional(CONF_TO): selector.TextSelector(selector.TextSelectorConfig()), + vol.Optional(CONF_TIME): selector.TimeSelector(selector.TimeSelectorConfig()), + vol.Required(CONF_WEEKDAY, default=WEEKDAYS): selector.SelectSelector( + selector.SelectSelectorConfig( + options=WEEKDAYS, + multiple=True, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + } +) +DATA_SCHEMA_REAUTH = vol.Schema( + { + vol.Required(CONF_API_KEY): selector.TextSelector( + selector.TextSelectorConfig() + ), + } +) + + +class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Trafikverket Ferry integration.""" + + VERSION = 1 + + entry: config_entries.ConfigEntry | None + + async def validate_input( + self, api_key: str, ferry_from: str, ferry_to: str + ) -> None: + """Validate input from user input.""" + web_session = async_get_clientsession(self.hass) + ferry_api = TrafikverketFerry(web_session, api_key) + await ferry_api.async_get_next_ferry_stop(ferry_from, ferry_to) + + async def async_step_reauth( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle re-authentication with Trafikverket.""" + + self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm re-authentication with Trafikverket.""" + errors: dict[str, str] = {} + + if user_input: + api_key = user_input[CONF_API_KEY] + + assert self.entry is not None + try: + await self.validate_input( + api_key, self.entry.data[CONF_FROM], self.entry.data[CONF_TO] + ) + except ValueError as err: + if str(err) == ERROR_INVALID_AUTH: + errors["base"] = "invalid_auth" + elif str(err) == ERROR_INVALID_ROUTE: + errors["base"] = "invalid_route" + else: + errors["base"] = "cannot_connect" + else: + self.hass.config_entries.async_update_entry( + self.entry, + data={ + **self.entry.data, + CONF_API_KEY: api_key, + }, + ) + await self.hass.config_entries.async_reload(self.entry.entry_id) + return self.async_abort(reason="reauth_successful") + + return self.async_show_form( + step_id="reauth_confirm", + data_schema=DATA_SCHEMA_REAUTH, + errors=errors, + ) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the user step.""" + errors: dict[str, str] = {} + + if user_input is not None: + api_key: str = user_input[CONF_API_KEY] + ferry_from: str = user_input[CONF_FROM] + ferry_to: str = user_input.get(CONF_TO, "") + ferry_time: str = user_input[CONF_TIME] + weekdays: list[str] = user_input[CONF_WEEKDAY] + + name = f"{ferry_from}" + if ferry_to: + name = name + f" to {ferry_to}" + if ferry_time != "00:00:00": + name = name + f" at {str(ferry_time)}" + + try: + await self.validate_input(api_key, ferry_from, ferry_to) + except ValueError as err: + if str(err) == ERROR_INVALID_AUTH: + errors["base"] = "invalid_auth" + elif str(err) == ERROR_INVALID_ROUTE: + errors["base"] = "invalid_route" + else: + errors["base"] = "cannot_connect" + else: + if not errors: + unique_id = create_unique_id( + ferry_from, + ferry_to, + ferry_time, + weekdays, + ) + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name, + data={ + CONF_API_KEY: api_key, + CONF_NAME: name, + CONF_FROM: ferry_from, + CONF_TO: ferry_to, + CONF_TIME: ferry_time, + CONF_WEEKDAY: weekdays, + }, + ) + + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors=errors, + ) diff --git a/homeassistant/components/trafikverket_ferry/const.py b/homeassistant/components/trafikverket_ferry/const.py new file mode 100644 index 00000000000..cdcec530d08 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/const.py @@ -0,0 +1,11 @@ +"""Adds constants for Trafikverket Ferry integration.""" +from homeassistant.const import Platform + +DOMAIN = "trafikverket_ferry" +PLATFORMS = [Platform.SENSOR] +ATTRIBUTION = "Data provided by Trafikverket" + +CONF_TRAINS = "trains" +CONF_FROM = "from" +CONF_TO = "to" +CONF_TIME = "time" diff --git a/homeassistant/components/trafikverket_ferry/coordinator.py b/homeassistant/components/trafikverket_ferry/coordinator.py new file mode 100644 index 00000000000..052341cd12e --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/coordinator.py @@ -0,0 +1,93 @@ +"""DataUpdateCoordinator for the Trafikverket Ferry integration.""" +from __future__ import annotations + +from datetime import date, datetime, time, timedelta +import logging +from typing import Any + +from pytrafikverket import TrafikverketFerry +from pytrafikverket.trafikverket_ferry import FerryStop + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_WEEKDAY, WEEKDAYS +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.util.dt import UTC, as_utc, parse_time + +from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN + +_LOGGER = logging.getLogger(__name__) +TIME_BETWEEN_UPDATES = timedelta(minutes=5) + + +def next_weekday(fromdate: date, weekday: int) -> date: + """Return the date of the next time a specific weekday happen.""" + days_ahead = weekday - fromdate.weekday() + if days_ahead <= 0: + days_ahead += 7 + return fromdate + timedelta(days_ahead) + + +def next_departuredate(departure: list[str]) -> date: + """Calculate the next departuredate from an array input of short days.""" + today_date = date.today() + today_weekday = date.weekday(today_date) + if WEEKDAYS[today_weekday] in departure: + return today_date + for day in departure: + next_departure = WEEKDAYS.index(day) + if next_departure > today_weekday: + return next_weekday(today_date, next_departure) + return next_weekday(today_date, WEEKDAYS.index(departure[0])) + + +class TVDataUpdateCoordinator(DataUpdateCoordinator): + """A Trafikverket Data Update Coordinator.""" + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + """Initialize the Trafikverket coordinator.""" + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=TIME_BETWEEN_UPDATES, + ) + self._ferry_api = TrafikverketFerry( + async_get_clientsession(hass), entry.data[CONF_API_KEY] + ) + self._from: str = entry.data[CONF_FROM] + self._to: str = entry.data[CONF_TO] + self._time: time | None = parse_time(entry.data[CONF_TIME]) + self._weekdays: list[str] = entry.data[CONF_WEEKDAY] + + async def _async_update_data(self) -> dict[str, Any]: + """Fetch data from Trafikverket.""" + + departure_day = next_departuredate(self._weekdays) + currenttime = datetime.now() + when = ( + datetime.combine(departure_day, self._time) + if self._time + else datetime.now() + ) + if currenttime > when: + when = currenttime + + try: + routedata: FerryStop = await self._ferry_api.async_get_next_ferry_stop( + self._from, self._to, when + ) + except ValueError as error: + raise UpdateFailed( + f"Departure {when} encountered a problem: {error}" + ) from error + + states = { + "departure_time": routedata.departure_time.replace(tzinfo=UTC), + "departure_from": routedata.from_harbor_name, + "departure_to": routedata.to_harbor_name, + "departure_modified": as_utc(routedata.modified_time.replace(tzinfo=UTC)), + "departure_information": routedata.other_information, + } + return states diff --git a/homeassistant/components/trafikverket_ferry/manifest.json b/homeassistant/components/trafikverket_ferry/manifest.json new file mode 100644 index 00000000000..90864c7e358 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "trafikverket_ferry", + "name": "Trafikverket Ferry", + "documentation": "https://www.home-assistant.io/integrations/trafikverket_ferry", + "requirements": ["pytrafikverket==0.1.6.2"], + "codeowners": ["@gjohansson-ST"], + "config_flow": true, + "iot_class": "cloud_polling", + "loggers": ["pytrafikverket"] +} diff --git a/homeassistant/components/trafikverket_ferry/sensor.py b/homeassistant/components/trafikverket_ferry/sensor.py new file mode 100644 index 00000000000..682c2073f76 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/sensor.py @@ -0,0 +1,143 @@ +"""Ferry information for departures, provided by Trafikverket.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import timedelta +import logging +from typing import Any + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import ATTRIBUTION, DOMAIN +from .coordinator import TVDataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + +ATTR_FROM = "from_harbour" +ATTR_TO = "to_harbour" +ATTR_MODIFIED_TIME = "modified_time" +ATTR_OTHER_INFO = "other_info" + +ICON = "mdi:ferry" +SCAN_INTERVAL = timedelta(minutes=5) + + +@dataclass +class TrafikverketRequiredKeysMixin: + """Mixin for required keys.""" + + value_fn: Callable[[dict[str, Any]], StateType] + info_fn: Callable[[dict[str, Any]], StateType | list] + + +@dataclass +class TrafikverketSensorEntityDescription( + SensorEntityDescription, TrafikverketRequiredKeysMixin +): + """Describes Trafikverket sensor entity.""" + + +SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( + TrafikverketSensorEntityDescription( + key="departure_time", + name="Departure Time", + icon="mdi:clock", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda data: data["departure_time"], + info_fn=lambda data: data["departure_information"], + ), + TrafikverketSensorEntityDescription( + key="departure_from", + name="Departure From", + icon="mdi:ferry", + value_fn=lambda data: data["departure_from"], + info_fn=lambda data: data["departure_information"], + ), + TrafikverketSensorEntityDescription( + key="departure_to", + name="Departure To", + icon="mdi:ferry", + value_fn=lambda data: data["departure_to"], + info_fn=lambda data: data["departure_information"], + ), + TrafikverketSensorEntityDescription( + key="departure_modified", + name="Departure Modified", + icon="mdi:clock", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda data: data["departure_modified"], + info_fn=lambda data: data["departure_information"], + entity_registry_enabled_default=False, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the Trafikverket sensor entry.""" + + coordinator: TVDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + [ + FerrySensor(coordinator, entry.data[CONF_NAME], entry.entry_id, description) + for description in SENSOR_TYPES + ] + ) + + +class FerrySensor(CoordinatorEntity[TVDataUpdateCoordinator], SensorEntity): + """Contains data about a ferry departure.""" + + entity_description: TrafikverketSensorEntityDescription + _attr_attribution = ATTRIBUTION + + def __init__( + self, + coordinator: TVDataUpdateCoordinator, + name: str, + entry_id: str, + entity_description: TrafikverketSensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + self._attr_name = f"{name} {entity_description.name}" + self._attr_unique_id = f"{entry_id}-{entity_description.key}" + self.entity_description = entity_description + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, entry_id)}, + manufacturer="Trafikverket", + model="v1.2", + name=name, + configuration_url="https://api.trafikinfo.trafikverket.se/", + ) + self._update_attr() + + def _update_attr(self) -> None: + """Update _attr.""" + self._attr_native_value = self.entity_description.value_fn( + self.coordinator.data + ) + self._attr_extra_state_attributes = { + "other_information": self.entity_description.info_fn(self.coordinator.data), + } + + @callback + def _handle_coordinator_update(self) -> None: + self._update_attr() + return super()._handle_coordinator_update() diff --git a/homeassistant/components/trafikverket_ferry/strings.json b/homeassistant/components/trafikverket_ferry/strings.json new file mode 100644 index 00000000000..67200eae135 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/strings.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "invalid_route": "Could not find route with provided information", + "incorrect_api_key": "Invalid API key for selected account" + }, + "step": { + "user": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "to": "To Harbour", + "from": "From Harbour", + "time": "Time", + "weekday": "Weekdays" + } + }, + "reauth_confirm": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]" + } + } + } + } +} diff --git a/homeassistant/components/trafikverket_ferry/translations/de.json b/homeassistant/components/trafikverket_ferry/translations/de.json new file mode 100644 index 00000000000..298a0e1711c --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/de.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "incorrect_api_key": "Ung\u00fcltiger API-Schl\u00fcssel f\u00fcr ausgew\u00e4hltes Konto", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_route": "Konnte keine Route mit den angegebenen Informationen finden" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-Schl\u00fcssel" + } + }, + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "from": "Vom Hafen", + "time": "Zeit", + "to": "Zum Hafen", + "weekday": "Wochentage" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/el.json b/homeassistant/components/trafikverket_ferry/translations/el.json new file mode 100644 index 00000000000..552e8ad0160 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/el.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "incorrect_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_route": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae\u03c2 \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03b5\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + } + }, + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "from": "\u0391\u03c0\u03cc \u03c4\u03bf \u039b\u03b9\u03bc\u03ac\u03bd\u03b9", + "time": "\u038f\u03c1\u03b1", + "to": "\u03a0\u03c1\u03bf\u03c2 \u039b\u03b9\u03bc\u03ac\u03bd\u03b9", + "weekday": "\u0395\u03c1\u03b3\u03ac\u03c3\u03b9\u03bc\u03b5\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/en.json b/homeassistant/components/trafikverket_ferry/translations/en.json new file mode 100644 index 00000000000..651f3476710 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/en.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" + }, + "error": { + "cannot_connect": "Failed to connect", + "incorrect_api_key": "Invalid API key for selected account", + "invalid_auth": "Invalid authentication", + "invalid_route": "Could not find route with provided information" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API Key" + } + }, + "user": { + "data": { + "api_key": "API Key", + "from": "From Harbour", + "time": "Time", + "to": "To Harbour", + "weekday": "Weekdays" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/et.json b/homeassistant/components/trafikverket_ferry/translations/et.json new file mode 100644 index 00000000000..ec791013788 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Konto on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "incorrect_api_key": "Valitud konto API-v\u00f5ti on kehtetu", + "invalid_auth": "Tuvastamine nurjus", + "invalid_route": "Esitatud teabega marsruuti ei leitud" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API v\u00f5ti" + } + }, + "user": { + "data": { + "api_key": "API v\u00f5ti", + "from": "Sadamast", + "time": "Kellaaeg", + "to": "Sadamasse", + "weekday": "N\u00e4dalap\u00e4ev" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/fr.json b/homeassistant/components/trafikverket_ferry/translations/fr.json new file mode 100644 index 00000000000..4288f56908f --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/fr.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "incorrect_api_key": "Cl\u00e9 d'API non valide pour le compte s\u00e9lectionn\u00e9", + "invalid_auth": "Authentification non valide", + "invalid_route": "Impossible de trouver un itin\u00e9raire avec les informations fournies" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Cl\u00e9 d'API" + } + }, + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "from": "Port de d\u00e9part", + "time": "Heure", + "to": "Port d'arriv\u00e9e", + "weekday": "Jours de la semaine" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/hu.json b/homeassistant/components/trafikverket_ferry/translations/hu.json new file mode 100644 index 00000000000..88b2b2ebfa7 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/hu.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "incorrect_api_key": "\u00c9rv\u00e9nytelen API-kulcs a megadott fi\u00f3khoz", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_route": "Nem tal\u00e1lhat\u00f3 \u00fatvonal a megadott inform\u00e1ci\u00f3k alapj\u00e1n" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API kulcs" + } + }, + "user": { + "data": { + "api_key": "API kulcs", + "from": "Kik\u00f6t\u0151b\u0151l", + "time": "Id\u0151pont", + "to": "Kik\u00f6t\u0151be", + "weekday": "H\u00e9tk\u00f6znap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/id.json b/homeassistant/components/trafikverket_ferry/translations/id.json new file mode 100644 index 00000000000..1d476f2c0e1 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "incorrect_api_key": "Kunci API tidak valid untuk akun yang dipilih", + "invalid_auth": "Autentikasi tidak valid", + "invalid_route": "Tidak dapat menemukan rute dengan informasi yang ditentukan" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Kunci API" + } + }, + "user": { + "data": { + "api_key": "Kunci API", + "from": "Dari Pelabuhan", + "time": "Waktu", + "to": "Ke Pelabuhan", + "weekday": "Hari kerja" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/it.json b/homeassistant/components/trafikverket_ferry/translations/it.json new file mode 100644 index 00000000000..160953d9f82 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/it.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "incorrect_api_key": "Chiave API non valida per l'account selezionato", + "invalid_auth": "Autenticazione non valida", + "invalid_route": "Impossibile trovare il percorso con le informazioni fornite" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chiave API" + } + }, + "user": { + "data": { + "api_key": "Chiave API", + "from": "Dal porto", + "time": "Orario", + "to": "Al porto", + "weekday": "Giorni della settimana" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/nl.json b/homeassistant/components/trafikverket_ferry/translations/nl.json new file mode 100644 index 00000000000..f84232839c8 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/nl.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "incorrect_api_key": "Ongeldige API-sleutel voor geselecteerde account", + "invalid_auth": "Ongeldige authenticatie", + "invalid_route": "Kon geen route vinden met verstrekte informatie" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-sleutel" + } + }, + "user": { + "data": { + "api_key": "API-sleutel", + "from": "Van de haven", + "time": "Tijd", + "to": "Naar de haven", + "weekday": "Weekdagen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/no.json b/homeassistant/components/trafikverket_ferry/translations/no.json new file mode 100644 index 00000000000..4cc6286d78f --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/no.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "incorrect_api_key": "Ugyldig API-n\u00f8kkel for valgt konto", + "invalid_auth": "Ugyldig godkjenning", + "invalid_route": "Kunne ikke finne rute med oppgitt informasjon" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-n\u00f8kkel" + } + }, + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "from": "Fra havnen", + "time": "Tid", + "to": "Til havn", + "weekday": "Hverdager" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/pl.json b/homeassistant/components/trafikverket_ferry/translations/pl.json new file mode 100644 index 00000000000..5e7d133b1e5 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/pl.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "incorrect_api_key": "Nieprawid\u0142owy klucz API dla wybranego konta", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_route": "Nie mo\u017cna znale\u017a\u0107 trasy dla podanych informacjami" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Klucz API" + } + }, + "user": { + "data": { + "api_key": "Klucz API", + "from": "Z portu", + "time": "Czas", + "to": "Do portu", + "weekday": "Dni powszednie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/pt-BR.json b/homeassistant/components/trafikverket_ferry/translations/pt-BR.json new file mode 100644 index 00000000000..e2349c181c8 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "incorrect_api_key": "Chave de API inv\u00e1lida para a conta selecionada", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_route": "N\u00e3o foi poss\u00edvel encontrar a rota com as informa\u00e7\u00f5es fornecidas" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + } + }, + "user": { + "data": { + "api_key": "Chave da API", + "from": "Do porto", + "time": "Tempo", + "to": "Ao porto", + "weekday": "Dias \u00fateis" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/ru.json b/homeassistant/components/trafikverket_ferry/translations/ru.json new file mode 100644 index 00000000000..c87b6e941f3 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/ru.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "incorrect_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0434\u043b\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_route": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0441 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439." + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + } + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "from": "\u0418\u0437 \u0433\u0430\u0432\u0430\u043d\u0438", + "time": "\u0412\u0440\u0435\u043c\u044f", + "to": "\u0412 \u0433\u0430\u0432\u0430\u043d\u044c", + "weekday": "\u0411\u0443\u0434\u043d\u0438\u0435 \u0434\u043d\u0438" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/translations/zh-Hant.json b/homeassistant/components/trafikverket_ferry/translations/zh-Hant.json new file mode 100644 index 00000000000..9acd15fdfb1 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/translations/zh-Hant.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "incorrect_api_key": "\u6240\u9078\u64c7\u5e33\u865f\u4e4b API \u91d1\u9470\u7121\u6548", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_route": "\u627e\u4e0d\u5230\u63d0\u4f9b\u8cc7\u8a0a\u4e4b\u8def\u7dda" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API \u91d1\u9470" + } + }, + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "from": "\u81ea\u6d77\u6e2f", + "time": "\u6642\u9593", + "to": "\u81f3\u6d77\u6e2f", + "weekday": "\u5e73\u65e5" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_ferry/util.py b/homeassistant/components/trafikverket_ferry/util.py new file mode 100644 index 00000000000..a78f6f82f1a --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/util.py @@ -0,0 +1,14 @@ +"""Utils for trafikverket_ferry.""" +from __future__ import annotations + +from datetime import time + + +def create_unique_id( + ferry_from: str, ferry_to: str, ferry_time: time | str | None, weekdays: list[str] +) -> str: + """Create unique id.""" + return ( + f"{ferry_from.casefold().replace(' ', '')}-{ferry_to.casefold().replace(' ', '')}" + f"-{str(ferry_time)}-{str(weekdays)}" + ) diff --git a/homeassistant/components/trafikverket_train/translations/bg.json b/homeassistant/components/trafikverket_train/translations/bg.json new file mode 100644 index 00000000000..91bb9c04e35 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/bg.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" + }, + "error": { + "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", + "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447" + } + }, + "user": { + "data": { + "api_key": "API \u043a\u043b\u044e\u0447", + "weekday": "\u0414\u043d\u0438" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/ca.json b/homeassistant/components/trafikverket_train/translations/ca.json new file mode 100644 index 00000000000..ed9a28841a2 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/ca.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja est\u00e0 configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "incorrect_api_key": "Clau API inv\u00e0lida per al compte seleccionat", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_station": "No s'ha trobat cap estaci\u00f3 amb el nom especificat", + "invalid_time": "Hora proporcionada inv\u00e0lida", + "more_stations": "S'han trobat m\u00faltiples estacions amb el nom especificat" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Clau API" + } + }, + "user": { + "data": { + "api_key": "Clau API", + "from": "Des de l'estaci\u00f3", + "time": "Hora (opcional)", + "to": "A l'estaci\u00f3", + "weekday": "Dies" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/de.json b/homeassistant/components/trafikverket_train/translations/de.json new file mode 100644 index 00000000000..057a416d70c --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/de.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "incorrect_api_key": "Ung\u00fcltiger API-Schl\u00fcssel f\u00fcr ausgew\u00e4hltes Konto", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_station": "Es konnte kein Bahnhof mit dem angegebenen Namen gefunden werden", + "invalid_time": "Ung\u00fcltige Zeitangabe", + "more_stations": "Mehrere Bahnh\u00f6fe mit dem angegebenen Namen gefunden" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-Schl\u00fcssel" + } + }, + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "from": "Vom Bahnhof", + "time": "Zeit (optional)", + "to": "Zum Bahnhof", + "weekday": "Tage" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/el.json b/homeassistant/components/trafikverket_train/translations/el.json new file mode 100644 index 00000000000..1dea0a7a8da --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/el.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03b8\u03b5\u03af", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "incorrect_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc", + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "invalid_station": "\u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03cd \u03bc\u03b5 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1", + "invalid_time": "\u0394\u03cc\u03b8\u03b7\u03ba\u03b5 \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03ce\u03c1\u03b1", + "more_stations": "\u0392\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03c0\u03bf\u03bb\u03bb\u03b1\u03c0\u03bb\u03bf\u03af \u03c3\u03c4\u03b1\u03b8\u03bc\u03bf\u03af \u03bc\u03b5 \u03c4\u03bf \u03ba\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + } + }, + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API", + "from": "\u0391\u03c0\u03cc \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc", + "time": "\u03a7\u03c1\u03cc\u03bd\u03bf\u03c2 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "to": "\u03a0\u03c1\u03bf\u03c2 \u03c3\u03c4\u03b1\u03b8\u03bc\u03cc", + "weekday": "\u0397\u03bc\u03ad\u03c1\u03b5\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/en.json b/homeassistant/components/trafikverket_train/translations/en.json index dc91c2097f3..86969fc7d96 100644 --- a/homeassistant/components/trafikverket_train/translations/en.json +++ b/homeassistant/components/trafikverket_train/translations/en.json @@ -6,26 +6,26 @@ }, "error": { "cannot_connect": "Failed to connect", + "incorrect_api_key": "Invalid API key for selected account", "invalid_auth": "Invalid authentication", "invalid_station": "Could not find a station with the specified name", - "more_stations": "Found multiple stations with the specified name", "invalid_time": "Invalid time provided", - "incorrect_api_key": "Invalid API key for selected account" + "more_stations": "Found multiple stations with the specified name" }, "step": { - "user": { - "data": { - "api_key": "API Key", - "to": "To station", - "from": "From station", - "time": "Time (optional)", - "weekday": "Days" - } - }, "reauth_confirm": { "data": { "api_key": "API Key" } + }, + "user": { + "data": { + "api_key": "API Key", + "from": "From station", + "time": "Time (optional)", + "to": "To station", + "weekday": "Days" + } } } } diff --git a/homeassistant/components/trafikverket_train/translations/et.json b/homeassistant/components/trafikverket_train/translations/et.json new file mode 100644 index 00000000000..9712adacce7 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/et.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Konto on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "incorrect_api_key": "Valitud konto API-v\u00f5ti on kehtetu", + "invalid_auth": "Tuvastamine nurjus", + "invalid_station": "M\u00e4\u00e4ratud nimega jaama ei leitud.", + "invalid_time": "Esitatud on kehtetu aeg", + "more_stations": "Leiti mitu m\u00e4\u00e4ratud nimega jaama" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API v\u00f5ti" + } + }, + "user": { + "data": { + "api_key": "API v\u00f5ti", + "from": "L\u00e4htejaam", + "time": "Aeg (valikuline)", + "to": "Sihtjaam", + "weekday": "N\u00e4dalap\u00e4evad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/fr.json b/homeassistant/components/trafikverket_train/translations/fr.json new file mode 100644 index 00000000000..357edca46fa --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/fr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "incorrect_api_key": "Cl\u00e9 d'API non valide pour le compte s\u00e9lectionn\u00e9", + "invalid_auth": "Authentification non valide", + "invalid_station": "Aucune gare portant le nom sp\u00e9cifi\u00e9 n'a \u00e9t\u00e9 trouv\u00e9e", + "invalid_time": "Heure fournie non valide", + "more_stations": "Plusieurs gares portant le nom sp\u00e9cifi\u00e9 ont \u00e9t\u00e9 trouv\u00e9es" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Cl\u00e9 d'API" + } + }, + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "from": "Gare de d\u00e9part", + "time": "Heure (facultatif)", + "to": "Gare d'arriv\u00e9e", + "weekday": "Jours" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/he.json b/homeassistant/components/trafikverket_train/translations/he.json new file mode 100644 index 00000000000..29f4fc720da --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/he.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4", + "reauth_successful": "\u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05de\u05d7\u05d3\u05e9 \u05d4\u05e6\u05dc\u05d9\u05d7" + }, + "error": { + "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API" + } + }, + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/hu.json b/homeassistant/components/trafikverket_train/translations/hu.json new file mode 100644 index 00000000000..ff4a4ee7142 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/hu.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "incorrect_api_key": "\u00c9rv\u00e9nytelen API-kulcs a megadott fi\u00f3khoz", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_station": "Nem tal\u00e1lhat\u00f3 megadott nev\u0171 \u00e1llom\u00e1s", + "invalid_time": "\u00c9rv\u00e9nytelen megadott id\u0151", + "more_stations": "T\u00f6bb \u00e1llom\u00e1s tal\u00e1lhat\u00f3 a megadott n\u00e9vvel" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API kulcs" + } + }, + "user": { + "data": { + "api_key": "API kulcs", + "from": "\u00c1llom\u00e1sr\u00f3l", + "time": "Id\u0151 (opcion\u00e1lis)", + "to": "\u00c1llom\u00e1sig", + "weekday": "Napok" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/id.json b/homeassistant/components/trafikverket_train/translations/id.json new file mode 100644 index 00000000000..4723183257c --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "incorrect_api_key": "Kunci API tidak valid untuk akun yang dipilih", + "invalid_auth": "Autentikasi tidak valid", + "invalid_station": "Tidak dapat menemukan SPBU dengan nama yang ditentukan", + "invalid_time": "Waktu yang disediakan tidak valid", + "more_stations": "Ditemukan beberapa SPBU dengan nama yang ditentukan" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Kunci API" + } + }, + "user": { + "data": { + "api_key": "Kunci API", + "from": "Dari stasiun", + "time": "Waktu (opsional)", + "to": "Ke stasiun", + "weekday": "Hari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/it.json b/homeassistant/components/trafikverket_train/translations/it.json new file mode 100644 index 00000000000..fe9c37e8e17 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/it.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "incorrect_api_key": "Chiave API non valida per l'account selezionato", + "invalid_auth": "Autenticazione non valida", + "invalid_station": "Impossibile trovare una stazione con il nome specificato", + "invalid_time": "Tempo fornito non valido", + "more_stations": "Trovate pi\u00f9 stazioni con il nome specificato" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chiave API" + } + }, + "user": { + "data": { + "api_key": "Chiave API", + "from": "Dalla stazione", + "time": "Tempo (facoltativo)", + "to": "Alla stazione", + "weekday": "Tariffe supportate" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/ja.json b/homeassistant/components/trafikverket_train/translations/ja.json new file mode 100644 index 00000000000..ec4b5a4015a --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/ja.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", + "incorrect_api_key": "\u9078\u629e\u3057\u305f\u30a2\u30ab\u30a6\u30f3\u30c8\u306eAPI\u30ad\u30fc\u304c\u7121\u52b9\u3067\u3059", + "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c", + "invalid_station": "\u6307\u5b9a\u3055\u308c\u305f\u540d\u524d\u306e\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f", + "invalid_time": "\u7121\u52b9\u306a\u6642\u9593\u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f", + "more_stations": "\u6307\u5b9a\u3055\u308c\u305f\u540d\u524d\u306e\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u3092\u8907\u6570\u767a\u898b" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API\u30ad\u30fc" + } + }, + "user": { + "data": { + "api_key": "API\u30ad\u30fc", + "from": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u304b\u3089", + "time": "\u6642\u9593\uff08\u30aa\u30d7\u30b7\u30e7\u30f3\uff09", + "to": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u3078", + "weekday": "\u65e5" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/nl.json b/homeassistant/components/trafikverket_train/translations/nl.json new file mode 100644 index 00000000000..d076f8b2961 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "incorrect_api_key": "Ongeldige API-sleutel voor geselecteerd account", + "invalid_auth": "Ongeldige authenticatie", + "invalid_station": "Kon geen station vinden met de opgegeven naam", + "invalid_time": "Ongeldige tijd opgegeven", + "more_stations": "Meerdere stations gevonden met de opgegeven naam" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-sleutel" + } + }, + "user": { + "data": { + "api_key": "API-sleutel", + "from": "Van station", + "time": "Tijd (optioneel)", + "to": "Naar station", + "weekday": "Dagen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/no.json b/homeassistant/components/trafikverket_train/translations/no.json new file mode 100644 index 00000000000..12feb2f6abf --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/no.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "incorrect_api_key": "Ugyldig API-n\u00f8kkel for valgt konto", + "invalid_auth": "Ugyldig godkjenning", + "invalid_station": "Kunne ikke finne en stasjon med det angitte navnet", + "invalid_time": "Ugyldig tid oppgitt", + "more_stations": "Fant flere stasjoner med det angitte navnet" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API-n\u00f8kkel" + } + }, + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "from": "Fra stasjon", + "time": "Tid (valgfritt)", + "to": "Til stasjon", + "weekday": "Dager" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/pl.json b/homeassistant/components/trafikverket_train/translations/pl.json new file mode 100644 index 00000000000..0ff0f49c701 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/pl.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "incorrect_api_key": "Nieprawid\u0142owy klucz API dla wybranego konta", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_station": "Nie mo\u017cna znale\u017a\u0107 stacji o podanej nazwie", + "invalid_time": "Podano nieprawid\u0142owy czas", + "more_stations": "Znaleziono wiele stacji o podanej nazwie" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Klucz API" + } + }, + "user": { + "data": { + "api_key": "Klucz API", + "from": "Ze stacji", + "time": "Czas (opcjonalnie)", + "to": "Do stacji", + "weekday": "Dni" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/pt-BR.json b/homeassistant/components/trafikverket_train/translations/pt-BR.json new file mode 100644 index 00000000000..43dcf558a18 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/pt-BR.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "A conta j\u00e1 foi configurada", + "reauth_successful": "A reautentica\u00e7\u00e3o foi bem-sucedida" + }, + "error": { + "cannot_connect": "Falha ao conectar", + "incorrect_api_key": "Chave de API inv\u00e1lida para a conta selecionada", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_station": "N\u00e3o foi poss\u00edvel encontrar uma esta\u00e7\u00e3o com o nome especificado", + "invalid_time": "Tempo inv\u00e1lido fornecido", + "more_stations": "Encontrado v\u00e1rias esta\u00e7\u00f5es com o nome especificado" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "Chave da API" + } + }, + "user": { + "data": { + "api_key": "Chave da API", + "from": "Da esta\u00e7\u00e3o", + "time": "Tempo (opcional)", + "to": "Para esta\u00e7\u00e3o", + "weekday": "Dias" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/ru.json b/homeassistant/components/trafikverket_train/translations/ru.json new file mode 100644 index 00000000000..27a0b1d6393 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/ru.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "incorrect_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0434\u043b\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_station": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0441\u0442\u0430\u043d\u0446\u0438\u044e \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c.", + "invalid_time": "\u0423\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f.", + "more_stations": "\u041d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u043d\u0446\u0438\u0439 \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c." + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + } + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "from": "\u041e\u0442 \u0441\u0442\u0430\u043d\u0446\u0438\u0438", + "time": "\u0412\u0440\u0435\u043c\u044f (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "to": "\u041d\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044e", + "weekday": "\u0414\u043d\u0438" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/tr.json b/homeassistant/components/trafikverket_train/translations/tr.json new file mode 100644 index 00000000000..9e7f21b5685 --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/tr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "incorrect_api_key": "Se\u00e7ilen hesap i\u00e7in ge\u00e7ersiz API anahtar\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_station": "Belirtilen ada sahip bir istasyon bulunamad\u0131", + "invalid_time": "Ge\u00e7ersiz s\u00fcre sa\u011fland\u0131", + "more_stations": "Belirtilen ada sahip birden fazla istasyon bulundu" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API Anahtar\u0131" + } + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "from": "\u0130stasyondan", + "time": "Zaman (iste\u011fe ba\u011fl\u0131)", + "to": "\u0130stasyona", + "weekday": "G\u00fcn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/trafikverket_train/translations/zh-Hant.json b/homeassistant/components/trafikverket_train/translations/zh-Hant.json new file mode 100644 index 00000000000..be3d51c19fd --- /dev/null +++ b/homeassistant/components/trafikverket_train/translations/zh-Hant.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "incorrect_api_key": "\u6240\u9078\u64c7\u5e33\u865f\u4e4b API \u91d1\u9470\u7121\u6548", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_station": "\u627e\u4e0d\u5230\u8a72\u6307\u5b9a\u540d\u7a31\u4e4b\u8eca\u7ad9", + "invalid_time": "\u63d0\u4f9b\u6642\u9593\u7121\u6548", + "more_stations": "\u6307\u5b9a\u540d\u7a31\u627e\u5230\u591a\u500b\u8eca\u7ad9" + }, + "step": { + "reauth_confirm": { + "data": { + "api_key": "API \u91d1\u9470" + } + }, + "user": { + "data": { + "api_key": "API \u91d1\u9470", + "from": "\u51fa\u767c\u52a0\u6cb9\u7ad9", + "time": "\u6642\u9593\uff08\u9078\u9805\uff09", + "to": "\u62b5\u9054\u52a0\u6cb9\u7ad9", + "weekday": "\u5929" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/hu.json b/homeassistant/components/transmission/translations/hu.json index 5e3dcfd2b6c..79a60dc2b5b 100644 --- a/homeassistant/components/transmission/translations/hu.json +++ b/homeassistant/components/transmission/translations/hu.json @@ -12,7 +12,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index c001fb6b89b..4628ec8768a 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -27,6 +27,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SERVICE_PLAY_MEDIA, ) +from homeassistant.components.media_source import generate_media_source_id from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DESCRIPTION, @@ -141,6 +142,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: cache_dir = conf.get(CONF_CACHE_DIR, DEFAULT_CACHE_DIR) time_memory = conf.get(CONF_TIME_MEMORY, DEFAULT_TIME_MEMORY) base_url = conf.get(CONF_BASE_URL) + if base_url is not None: + _LOGGER.warning( + "TTS base_url option is deprecated. Configure internal/external URL instead" + ) hass.data[BASE_URL_KEY] = base_url await tts.async_init_cache(use_cache, cache_dir, time_memory, base_url) @@ -198,27 +203,28 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: language = service.data.get(ATTR_LANGUAGE) options = service.data.get(ATTR_OPTIONS) - try: - url = await tts.async_get_url_path( - p_type, message, cache=cache, language=language, options=options - ) - except HomeAssistantError as err: - _LOGGER.error("Error on init TTS: %s", err) - return - - base = tts.base_url or get_url(hass) - url = base + url - - data = { - ATTR_MEDIA_CONTENT_ID: url, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_ENTITY_ID: entity_ids, + tts.process_options(p_type, language, options) + params = { + "message": message, } + if cache is not None: + params["cache"] = "true" if cache else "false" + if language is not None: + params["language"] = language + if options is not None: + params.update(options) await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, - data, + { + ATTR_ENTITY_ID: entity_ids, + ATTR_MEDIA_CONTENT_ID: generate_media_source_id( + DOMAIN, + str(yarl.URL.build(path=p_type, query=params)), + ), + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + }, blocking=True, context=service.context, ) @@ -344,24 +350,17 @@ class SpeechManager: PLATFORM_FORMAT.format(domain=engine, platform=DOMAIN) ) - async def async_get_url_path( + @callback + def process_options( self, engine: str, - message: str, - cache: bool | None = None, language: str | None = None, options: dict | None = None, - ) -> str: - """Get URL for play message. - - This method is a coroutine. - """ + ) -> tuple[str, dict | None]: + """Validate and process options.""" if (provider := self.providers.get(engine)) is None: raise HomeAssistantError(f"Provider {engine} not found") - msg_hash = hashlib.sha1(bytes(message, "utf-8")).hexdigest() - use_cache = cache if cache is not None else self.use_cache - # Languages language = language or provider.default_language if language is None or language not in provider.supported_languages: @@ -382,9 +381,25 @@ class SpeechManager: ] if invalid_opts: raise HomeAssistantError(f"Invalid options found: {invalid_opts}") - options_key = _hash_options(options) - else: - options_key = "-" + + return language, options + + async def async_get_url_path( + self, + engine: str, + message: str, + cache: bool | None = None, + language: str | None = None, + options: dict | None = None, + ) -> str: + """Get URL for play message. + + This method is a coroutine. + """ + language, options = self.process_options(engine, language, options) + options_key = _hash_options(options) if options else "-" + msg_hash = hashlib.sha1(bytes(message, "utf-8")).hexdigest() + use_cache = cache if cache is not None else self.use_cache key = KEY_PATTERN.format( msg_hash, language.replace("_", "-"), options_key, engine diff --git a/homeassistant/components/tts/media_source.py b/homeassistant/components/tts/media_source.py index 48bb43990ef..99ef3dd85a0 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -2,7 +2,7 @@ from __future__ import annotations import mimetypes -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from yarl import URL @@ -17,6 +17,7 @@ from homeassistant.components.media_source.models import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.network import get_url from .const import DOMAIN @@ -46,22 +47,27 @@ class TTSMediaSource(MediaSource): raise Unresolvable("No message specified.") options = dict(parsed.query) - kwargs = { + kwargs: dict[str, Any] = { "engine": parsed.name, "message": options.pop("message"), "language": options.pop("language", None), "options": options, } + if "cache" in options: + kwargs["cache"] = options.pop("cache") == "true" manager: SpeechManager = self.hass.data[DOMAIN] try: - url = await manager.async_get_url_path(**kwargs) # type: ignore[arg-type] + url = await manager.async_get_url_path(**kwargs) except HomeAssistantError as err: raise Unresolvable(str(err)) from err mime_type = mimetypes.guess_type(url)[0] or "audio/mpeg" + if manager.base_url and manager.base_url != get_url(self.hass): + url = f"{manager.base_url}{url}" + return PlayMedia(url, mime_type) async def async_browse_media( diff --git a/homeassistant/components/tuya/alarm_control_panel.py b/homeassistant/components/tuya/alarm_control_panel.py index 7838d4a2b4a..aca09131b4c 100644 --- a/homeassistant/components/tuya/alarm_control_panel.py +++ b/homeassistant/components/tuya/alarm_control_panel.py @@ -5,11 +5,9 @@ from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.backports.enum import StrEnum from homeassistant.components.alarm_control_panel import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_TRIGGER, AlarmControlPanelEntity, AlarmControlPanelEntityDescription, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -109,13 +107,13 @@ class TuyaAlarmEntity(TuyaEntity, AlarmControlPanelEntity): description.key, dptype=DPType.ENUM, prefer_function=True ): if Mode.HOME in supported_modes.range: - self._attr_supported_features |= SUPPORT_ALARM_ARM_HOME + self._attr_supported_features |= AlarmControlPanelEntityFeature.ARM_HOME if Mode.ARM in supported_modes.range: - self._attr_supported_features |= SUPPORT_ALARM_ARM_AWAY + self._attr_supported_features |= AlarmControlPanelEntityFeature.ARM_AWAY if Mode.SOS in supported_modes.range: - self._attr_supported_features |= SUPPORT_ALARM_TRIGGER + self._attr_supported_features |= AlarmControlPanelEntityFeature.TRIGGER @property def state(self): diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index 553b3cc9590..33b571ea848 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -45,11 +45,11 @@ class IntegerTypeData: def scale_value(self, value: float | int) -> float: """Scale a value.""" - return value * self.step / (10**self.scale) + return value / (10**self.scale) def scale_value_back(self, value: float | int) -> int: """Return raw value for scaled.""" - return int((value * (10**self.scale)) / self.step) + return int(value * (10**self.scale)) def remap_value_to( self, diff --git a/homeassistant/components/tuya/button.py b/homeassistant/components/tuya/button.py index f8cfe36cf91..3b4a2883266 100644 --- a/homeassistant/components/tuya/button.py +++ b/homeassistant/components/tuya/button.py @@ -53,6 +53,15 @@ BUTTONS: dict[str, tuple[ButtonEntityDescription, ...]] = { entity_category=EntityCategory.CONFIG, ), ), + # Wake Up Light II + # Not documented + "hxd": ( + ButtonEntityDescription( + key=DPCode.SWITCH_USB6, + name="Snooze", + icon="mdi:sleep", + ), + ), } diff --git a/homeassistant/components/tuya/camera.py b/homeassistant/components/tuya/camera.py index e67b220ea30..182d0dfd85d 100644 --- a/homeassistant/components/tuya/camera.py +++ b/homeassistant/components/tuya/camera.py @@ -4,7 +4,7 @@ from __future__ import annotations from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components import ffmpeg -from homeassistant.components.camera import SUPPORT_STREAM, Camera as CameraEntity +from homeassistant.components.camera import Camera as CameraEntity, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -50,7 +50,7 @@ async def async_setup_entry( class TuyaCameraEntity(TuyaEntity, CameraEntity): """Tuya Camera Entity.""" - _attr_supported_features = SUPPORT_STREAM + _attr_supported_features = CameraEntityFeature.STREAM _attr_brand = "Tuya" def __init__( diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index b70f81bc4d5..e111a1630ad 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -8,21 +8,13 @@ from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components.climate import ClimateEntity, ClimateEntityDescription from homeassistant.components.climate.const import ( - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, SWING_ON, SWING_VERTICAL, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -35,14 +27,14 @@ from .base import IntegerTypeData, TuyaEntity from .const import DOMAIN, LOGGER, TUYA_DISCOVERY_NEW, DPCode, DPType TUYA_HVAC_TO_HA = { - "auto": HVAC_MODE_HEAT_COOL, - "cold": HVAC_MODE_COOL, - "freeze": HVAC_MODE_COOL, - "heat": HVAC_MODE_HEAT, - "hot": HVAC_MODE_HEAT, - "manual": HVAC_MODE_HEAT_COOL, - "wet": HVAC_MODE_DRY, - "wind": HVAC_MODE_FAN_ONLY, + "auto": HVACMode.HEAT_COOL, + "cold": HVACMode.COOL, + "freeze": HVACMode.COOL, + "heat": HVACMode.HEAT, + "hot": HVACMode.HEAT, + "manual": HVACMode.HEAT_COOL, + "wet": HVACMode.DRY, + "wind": HVACMode.FAN_ONLY, } @@ -50,7 +42,7 @@ TUYA_HVAC_TO_HA = { class TuyaClimateSensorDescriptionMixin: """Define an entity description mixin for climate entities.""" - switch_only_hvac_mode: str + switch_only_hvac_mode: HVACMode @dataclass @@ -65,31 +57,31 @@ CLIMATE_DESCRIPTIONS: dict[str, TuyaClimateEntityDescription] = { # https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n "kt": TuyaClimateEntityDescription( key="kt", - switch_only_hvac_mode=HVAC_MODE_COOL, + switch_only_hvac_mode=HVACMode.COOL, ), # Heater # https://developer.tuya.com/en/docs/iot/f?id=K9gf46epy4j82 "qn": TuyaClimateEntityDescription( key="qn", - switch_only_hvac_mode=HVAC_MODE_HEAT, + switch_only_hvac_mode=HVACMode.HEAT, ), # Heater # https://developer.tuya.com/en/docs/iot/categoryrs?id=Kaiuz0nfferyx "rs": TuyaClimateEntityDescription( key="rs", - switch_only_hvac_mode=HVAC_MODE_HEAT, + switch_only_hvac_mode=HVACMode.HEAT, ), # Thermostat # https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9 "wk": TuyaClimateEntityDescription( key="wk", - switch_only_hvac_mode=HVAC_MODE_HEAT_COOL, + switch_only_hvac_mode=HVACMode.HEAT_COOL, ), # Thermostatic Radiator Valve # Not documented "wkf": TuyaClimateEntityDescription( key="wkf", - switch_only_hvac_mode=HVAC_MODE_HEAT, + switch_only_hvac_mode=HVACMode.HEAT, ), } @@ -201,25 +193,25 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): # Get integer type data for the dpcode to set temperature, use # it to define min, max & step temperatures if self._set_temperature: - self._attr_supported_features |= SUPPORT_TARGET_TEMPERATURE + self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE self._attr_max_temp = self._set_temperature.max_scaled self._attr_min_temp = self._set_temperature.min_scaled self._attr_target_temperature_step = self._set_temperature.step_scaled # Determine HVAC modes - self._attr_hvac_modes = [] + self._attr_hvac_modes: list[str] = [] self._hvac_to_tuya = {} if enum_type := self.find_dpcode( DPCode.MODE, dptype=DPType.ENUM, prefer_function=True ): - self._attr_hvac_modes = [HVAC_MODE_OFF] + self._attr_hvac_modes = [HVACMode.OFF] for tuya_mode, ha_mode in TUYA_HVAC_TO_HA.items(): if tuya_mode in enum_type.range: self._hvac_to_tuya[ha_mode] = tuya_mode self._attr_hvac_modes.append(ha_mode) elif self.find_dpcode(DPCode.SWITCH, prefer_function=True): self._attr_hvac_modes = [ - HVAC_MODE_OFF, + HVACMode.OFF, description.switch_only_hvac_mode, ] @@ -227,7 +219,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): if int_type := self.find_dpcode( DPCode.HUMIDITY_SET, dptype=DPType.INTEGER, prefer_function=True ): - self._attr_supported_features |= SUPPORT_TARGET_HUMIDITY + self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY self._set_humidity = int_type self._attr_min_humidity = int(int_type.min_scaled) self._attr_max_humidity = int(int_type.max_scaled) @@ -243,7 +235,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): dptype=DPType.ENUM, prefer_function=True, ): - self._attr_supported_features |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE self._attr_fan_modes = enum_type.range # Determine swing modes @@ -256,7 +248,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): ), prefer_function=True, ): - self._attr_supported_features |= SUPPORT_SWING_MODE + self._attr_supported_features |= ClimateEntityFeature.SWING_MODE self._attr_swing_modes = [SWING_OFF] if self.find_dpcode((DPCode.SHAKE, DPCode.SWING), prefer_function=True): self._attr_swing_modes.append(SWING_ON) @@ -283,9 +275,9 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): self.device.name, ) - def set_hvac_mode(self, hvac_mode: str) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - commands = [{"code": DPCode.SWITCH, "value": hvac_mode != HVAC_MODE_OFF}] + commands = [{"code": DPCode.SWITCH, "value": hvac_mode != HVACMode.OFF}] if hvac_mode in self._hvac_to_tuya: commands.append( {"code": DPCode.MODE, "value": self._hvac_to_tuya[hvac_mode]} @@ -411,24 +403,24 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): return round(self._set_humidity.scale_value(humidity)) @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac mode.""" # If the switch off, hvac mode is off as well. Unless the switch # the switch is on or doesn't exists of course... if not self.device.status.get(DPCode.SWITCH, True): - return HVAC_MODE_OFF + return HVACMode.OFF if DPCode.MODE not in self.device.function: if self.device.status.get(DPCode.SWITCH, False): return self.entity_description.switch_only_hvac_mode - return HVAC_MODE_OFF + return HVACMode.OFF if ( mode := self.device.status.get(DPCode.MODE) ) is not None and mode in TUYA_HVAC_TO_HA: return TUYA_HVAC_TO_HA[mode] - return HVAC_MODE_OFF + return HVACMode.OFF @property def fan_mode(self) -> str | None: @@ -461,7 +453,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): return # Fake turn on - for mode in (HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT, HVAC_MODE_COOL): + for mode in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL): if mode not in self.hvac_modes: continue self.set_hvac_mode(mode) @@ -474,5 +466,5 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): return # Fake turn off - if HVAC_MODE_OFF in self.hvac_modes: - self.set_hvac_mode(HVAC_MODE_OFF) + if HVACMode.OFF in self.hvac_modes: + self.set_hvac_mode(HVACMode.OFF) diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index de277e61510..e75b07b988d 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -9,14 +9,10 @@ from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, CoverEntityDescription, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -200,22 +196,24 @@ class TuyaCoverEntity(TuyaEntity, CoverEntity): # Check if this cover is based on a switch or has controls if self.find_dpcode(description.key, prefer_function=True): if device.function[description.key].type == "Boolean": - self._attr_supported_features |= SUPPORT_OPEN | SUPPORT_CLOSE + self._attr_supported_features |= ( + CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + ) elif enum_type := self.find_dpcode( description.key, dptype=DPType.ENUM, prefer_function=True ): if description.open_instruction_value in enum_type.range: - self._attr_supported_features |= SUPPORT_OPEN + self._attr_supported_features |= CoverEntityFeature.OPEN if description.close_instruction_value in enum_type.range: - self._attr_supported_features |= SUPPORT_CLOSE + self._attr_supported_features |= CoverEntityFeature.CLOSE if description.stop_instruction_value in enum_type.range: - self._attr_supported_features |= SUPPORT_STOP + self._attr_supported_features |= CoverEntityFeature.STOP # Determine type to use for setting the position if int_type := self.find_dpcode( description.set_position, dptype=DPType.INTEGER, prefer_function=True ): - self._attr_supported_features |= SUPPORT_SET_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_POSITION self._set_position = int_type # Set as default, unless overwritten below self._current_position = int_type @@ -232,7 +230,7 @@ class TuyaCoverEntity(TuyaEntity, CoverEntity): dptype=DPType.INTEGER, prefer_function=True, ): - self._attr_supported_features |= SUPPORT_SET_TILT_POSITION + self._attr_supported_features |= CoverEntityFeature.SET_TILT_POSITION self._tilt = int_type @property diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index be05acef3de..2d16ed36d40 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -8,11 +8,8 @@ from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components.fan import ( DIRECTION_FORWARD, DIRECTION_REVERSE, - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -85,7 +82,7 @@ class TuyaFanEntity(TuyaEntity, FanEntity): (DPCode.FAN_MODE, DPCode.MODE), dptype=DPType.ENUM, prefer_function=True ): self._presets = enum_type - self._attr_supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= FanEntityFeature.PRESET_MODE self._attr_preset_modes = enum_type.range # Find speed controls, can be either percentage or a set of speeds @@ -98,25 +95,25 @@ class TuyaFanEntity(TuyaEntity, FanEntity): if int_type := self.find_dpcode( dpcodes, dptype=DPType.INTEGER, prefer_function=True ): - self._attr_supported_features |= SUPPORT_SET_SPEED + self._attr_supported_features |= FanEntityFeature.SET_SPEED self._speed = int_type elif enum_type := self.find_dpcode( dpcodes, dptype=DPType.ENUM, prefer_function=True ): - self._attr_supported_features |= SUPPORT_SET_SPEED + self._attr_supported_features |= FanEntityFeature.SET_SPEED self._speeds = enum_type if dpcode := self.find_dpcode( (DPCode.SWITCH_HORIZONTAL, DPCode.SWITCH_VERTICAL), prefer_function=True ): self._oscillate = dpcode - self._attr_supported_features |= SUPPORT_OSCILLATE + self._attr_supported_features |= FanEntityFeature.OSCILLATE if enum_type := self.find_dpcode( DPCode.FAN_DIRECTION, dptype=DPType.ENUM, prefer_function=True ): self._direction = enum_type - self._attr_supported_features |= SUPPORT_DIRECTION + self._attr_supported_features |= FanEntityFeature.DIRECTION def set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the fan.""" diff --git a/homeassistant/components/tuya/humidifier.py b/homeassistant/components/tuya/humidifier.py index 4a56088a272..5fd33ba80d0 100644 --- a/homeassistant/components/tuya/humidifier.py +++ b/homeassistant/components/tuya/humidifier.py @@ -6,10 +6,10 @@ from dataclasses import dataclass from tuya_iot import TuyaDevice, TuyaDeviceManager from homeassistant.components.humidifier import ( - SUPPORT_MODES, HumidifierDeviceClass, HumidifierEntity, HumidifierEntityDescription, + HumidifierEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -112,7 +112,7 @@ class TuyaHumidifierEntity(TuyaEntity, HumidifierEntity): if enum_type := self.find_dpcode( DPCode.MODE, dptype=DPType.ENUM, prefer_function=True ): - self._attr_supported_features |= SUPPORT_MODES + self._attr_supported_features |= HumidifierEntityFeature.MODES self._attr_available_modes = enum_type.range @property diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index c4f874d0687..7ba8acdc0fe 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -11,10 +11,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, + ColorMode, LightEntity, LightEntityDescription, ) @@ -268,6 +265,17 @@ LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = { brightness=DPCode.BRIGHT_VALUE_2, ), ), + # Wake Up Light II + # Not documented + "hxd": ( + TuyaLightEntityDescription( + key=DPCode.SWITCH_LED, + name="Light", + brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE), + brightness_max=DPCode.BRIGHTNESS_MAX_1, + brightness_min=DPCode.BRIGHTNESS_MIN_1, + ), + ), # Solar Light # https://developer.tuya.com/en/docs/iot/tynd?id=Kaof8j02e1t98 "tyndj": ( @@ -401,7 +409,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): super().__init__(device, device_manager) self.entity_description = description self._attr_unique_id = f"{super().unique_id}{description.key}" - self._attr_supported_color_modes = {COLOR_MODE_ONOFF} + self._attr_supported_color_modes: set[ColorMode] = set() # Determine DPCodes self._color_mode_dpcode = self.find_dpcode( @@ -412,7 +420,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): description.brightness, dptype=DPType.INTEGER, prefer_function=True ): self._brightness = int_type - self._attr_supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) self._brightness_max = self.find_dpcode( description.brightness_max, dptype=DPType.INTEGER ) @@ -424,13 +432,13 @@ class TuyaLightEntity(TuyaEntity, LightEntity): description.color_temp, dptype=DPType.INTEGER, prefer_function=True ): self._color_temp = int_type - self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) if ( dpcode := self.find_dpcode(description.color_data, prefer_function=True) ) and self.get_dptype(dpcode) == DPType.JSON: self._color_data_dpcode = dpcode - self._attr_supported_color_modes.add(COLOR_MODE_HS) + self._attr_supported_color_modes.add(ColorMode.HS) if dpcode in self.device.function: values = cast(str, self.device.function[dpcode].values) else: @@ -451,6 +459,9 @@ class TuyaLightEntity(TuyaEntity, LightEntity): ): self._color_data_type = DEFAULT_COLOR_TYPE_DATA_V2 + if not self._attr_supported_color_modes: + self._attr_supported_color_modes = {ColorMode.ONOFF} + @property def is_on(self) -> bool: """Return true if light is on.""" @@ -484,7 +495,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): ] elif self._color_data_type and ( ATTR_HS_COLOR in kwargs - or (ATTR_BRIGHTNESS in kwargs and self.color_mode == COLOR_MODE_HS) + or (ATTR_BRIGHTNESS in kwargs and self.color_mode == ColorMode.HS) ): if self._color_mode_dpcode: commands += [ @@ -527,7 +538,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): if ( ATTR_BRIGHTNESS in kwargs - and self.color_mode != COLOR_MODE_HS + and self.color_mode != ColorMode.HS and self._brightness ): brightness = kwargs[ATTR_BRIGHTNESS] @@ -578,7 +589,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): def brightness(self) -> int | None: """Return the brightness of the light.""" # If the light is currently in color mode, extract the brightness from the color data - if self.color_mode == COLOR_MODE_HS and (color_data := self._get_color_data()): + if self.color_mode == ColorMode.HS and (color_data := self._get_color_data()): return color_data.brightness if not self._brightness: @@ -640,7 +651,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): return color_data.hs_color @property - def color_mode(self) -> str: + def color_mode(self) -> ColorMode: """Return the color_mode of the light.""" # We consider it to be in HS color mode, when work mode is anything # else than "white". @@ -648,12 +659,12 @@ class TuyaLightEntity(TuyaEntity, LightEntity): self._color_mode_dpcode and self.device.status.get(self._color_mode_dpcode) != WorkMode.WHITE ): - return COLOR_MODE_HS + return ColorMode.HS if self._color_temp: - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP if self._brightness: - return COLOR_MODE_BRIGHTNESS - return COLOR_MODE_ONOFF + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF def _get_color_data(self) -> ColorData | None: """Get current color data from device.""" diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index 24f9324fe5e..cf3808819e7 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/tuya", "requirements": ["tuya-iot-py-sdk==0.6.6"], "dependencies": ["ffmpeg"], - "codeowners": ["@Tuya", "@zlinoliver", "@METISU", "@frenck"], + "codeowners": ["@Tuya", "@zlinoliver", "@frenck"], "config_flow": true, "iot_class": "cloud_push", "dhcp": [ diff --git a/homeassistant/components/tuya/siren.py b/homeassistant/components/tuya/siren.py index ae38149da2b..dcb26871bb2 100644 --- a/homeassistant/components/tuya/siren.py +++ b/homeassistant/components/tuya/siren.py @@ -5,8 +5,11 @@ from typing import Any from tuya_iot import TuyaDevice, TuyaDeviceManager -from homeassistant.components.siren import SirenEntity, SirenEntityDescription -from homeassistant.components.siren.const import SUPPORT_TURN_OFF, SUPPORT_TURN_ON +from homeassistant.components.siren import ( + SirenEntity, + SirenEntityDescription, + SirenEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -71,6 +74,8 @@ async def async_setup_entry( class TuyaSirenEntity(TuyaEntity, SirenEntity): """Tuya Siren Entity.""" + _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF + def __init__( self, device: TuyaDevice, @@ -81,7 +86,6 @@ class TuyaSirenEntity(TuyaEntity, SirenEntity): super().__init__(device, device_manager) self.entity_description = description self._attr_unique_id = f"{super().unique_id}{description.key}" - self._attr_supported_features = SUPPORT_TURN_ON | SUPPORT_TURN_OFF @property def is_on(self) -> bool: diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index d978b377cc5..d587e8ea54b 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -98,6 +98,44 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { name="Switch", ), ), + # Wake Up Light II + # Not documented + "hxd": ( + SwitchEntityDescription( + key=DPCode.SWITCH_1, + name="Radio", + icon="mdi:radio", + ), + SwitchEntityDescription( + key=DPCode.SWITCH_2, + name="Alarm 1", + icon="mdi:alarm", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.SWITCH_3, + name="Alarm 2", + icon="mdi:alarm", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.SWITCH_4, + name="Alarm 3", + icon="mdi:alarm", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.SWITCH_5, + name="Alarm 4", + icon="mdi:alarm", + entity_category=EntityCategory.CONFIG, + ), + SwitchEntityDescription( + key=DPCode.SWITCH_6, + name="Sleep Aid", + icon="mdi:power-sleep", + ), + ), # Switch # https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s "kg": ( diff --git a/homeassistant/components/tuya/translations/select.he.json b/homeassistant/components/tuya/translations/select.he.json index f31e63515c8..5a29489bdcc 100644 --- a/homeassistant/components/tuya/translations/select.he.json +++ b/homeassistant/components/tuya/translations/select.he.json @@ -98,7 +98,7 @@ "power_on": "\u05de\u05d5\u05e4\u05e2\u05dc" }, "tuya__vacuum_cistern": { - "closed": "\u05e1\u05d2\u05d5\u05e8", + "closed": "\u05e0\u05e1\u05d2\u05e8", "high": "\u05d2\u05d1\u05d5\u05d4", "low": "\u05e0\u05de\u05d5\u05da", "middle": "\u05d0\u05de\u05e6\u05e2" diff --git a/homeassistant/components/tuya/translations/select.pt-BR.json b/homeassistant/components/tuya/translations/select.pt-BR.json index aed86dfe4ce..90ae768c418 100644 --- a/homeassistant/components/tuya/translations/select.pt-BR.json +++ b/homeassistant/components/tuya/translations/select.pt-BR.json @@ -109,19 +109,19 @@ "small": "Pequeno" }, "tuya__vacuum_mode": { - "bow": "", + "bow": "Bow", "chargego": "Retornar para Base", - "left_bow": "", - "left_spiral": "", + "left_bow": "Bow Left", + "left_spiral": "Spiral Left", "mop": "Esfregar (Mop)", "part": "Parcial", - "partial_bow": "", + "partial_bow": "Bow Partially", "pick_zone": "C\u00f4modos Selecionados", "point": "Ponto", "pose": "Ponto Definido", "random": "Aleat\u00f3rio", - "right_bow": "", - "right_spiral": "", + "right_bow": "Bow Right", + "right_spiral": "Spiral Right", "single": "Simples", "smart": "Autom\u00e1tica", "spiral": "Espiral", diff --git a/homeassistant/components/tuya/vacuum.py b/homeassistant/components/tuya/vacuum.py index 70e1691af92..6dea121c980 100644 --- a/homeassistant/components/tuya/vacuum.py +++ b/homeassistant/components/tuya/vacuum.py @@ -9,19 +9,8 @@ from homeassistant.components.vacuum import ( STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STATUS, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PAUSED @@ -95,39 +84,45 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity): """Init Tuya vacuum.""" super().__init__(device, device_manager) - self._supported_features |= SUPPORT_SEND_COMMAND + self._supported_features |= VacuumEntityFeature.SEND_COMMAND if self.find_dpcode(DPCode.PAUSE, prefer_function=True): - self._supported_features |= SUPPORT_PAUSE + self._supported_features |= VacuumEntityFeature.PAUSE if self.find_dpcode(DPCode.SWITCH_CHARGE, prefer_function=True): - self._supported_features |= SUPPORT_RETURN_HOME + self._supported_features |= VacuumEntityFeature.RETURN_HOME elif ( enum_type := self.find_dpcode( DPCode.MODE, dptype=DPType.ENUM, prefer_function=True ) ) and TUYA_MODE_RETURN_HOME in enum_type.range: - self._supported_features |= SUPPORT_RETURN_HOME + self._supported_features |= VacuumEntityFeature.RETURN_HOME if self.find_dpcode(DPCode.SEEK, prefer_function=True): - self._supported_features |= SUPPORT_LOCATE + self._supported_features |= VacuumEntityFeature.LOCATE if self.find_dpcode(DPCode.STATUS, prefer_function=True): - self._supported_features |= SUPPORT_STATE | SUPPORT_STATUS + self._supported_features |= ( + VacuumEntityFeature.STATE | VacuumEntityFeature.STATUS + ) if self.find_dpcode(DPCode.POWER, prefer_function=True): - self._supported_features |= SUPPORT_TURN_ON | SUPPORT_TURN_OFF + self._supported_features |= ( + VacuumEntityFeature.TURN_ON | VacuumEntityFeature.TURN_OFF + ) if self.find_dpcode(DPCode.POWER_GO, prefer_function=True): - self._supported_features |= SUPPORT_STOP | SUPPORT_START + self._supported_features |= ( + VacuumEntityFeature.STOP | VacuumEntityFeature.START + ) if enum_type := self.find_dpcode( DPCode.SUCTION, dptype=DPType.ENUM, prefer_function=True ): - self._supported_features |= SUPPORT_FAN_SPEED + self._supported_features |= VacuumEntityFeature.FAN_SPEED self._fan_speed = enum_type if int_type := self.find_dpcode(DPCode.ELECTRICITY_LEFT, dptype=DPType.INTEGER): - self._supported_features |= SUPPORT_BATTERY + self._supported_features |= VacuumEntityFeature.BATTERY self._battery_level = int_type @property @@ -205,7 +200,7 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity): self._send_command([{"code": DPCode.SUCTION, "value": fan_speed}]) def send_command( - self, command: str, params: list[str] | None = None, **kwargs: Any + self, command: str, params: dict | list | None = None, **kwargs: Any ) -> None: """Send raw command.""" if not params: diff --git a/homeassistant/components/twentemilieu/calendar.py b/homeassistant/components/twentemilieu/calendar.py index 0d2768e5eb2..1c7cfcbd4fc 100644 --- a/homeassistant/components/twentemilieu/calendar.py +++ b/homeassistant/components/twentemilieu/calendar.py @@ -2,9 +2,8 @@ from __future__ import annotations from datetime import datetime -from typing import Any -from homeassistant.components.calendar import CalendarEventDevice +from homeassistant.components.calendar import CalendarEntity, CalendarEvent from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback @@ -26,7 +25,7 @@ async def async_setup_entry( async_add_entities([TwenteMilieuCalendar(coordinator, entry)]) -class TwenteMilieuCalendar(TwenteMilieuEntity, CalendarEventDevice): +class TwenteMilieuCalendar(TwenteMilieuEntity, CalendarEntity): """Defines a Twente Milieu calendar.""" _attr_name = "Twente Milieu" @@ -40,26 +39,25 @@ class TwenteMilieuCalendar(TwenteMilieuEntity, CalendarEventDevice): """Initialize the Twente Milieu entity.""" super().__init__(coordinator, entry) self._attr_unique_id = str(entry.data[CONF_ID]) - self._event: dict[str, Any] | None = None + self._event: CalendarEvent | None = None @property - def event(self) -> dict[str, Any] | None: + def event(self) -> CalendarEvent | None: """Return the next upcoming event.""" return self._event async def async_get_events( self, hass: HomeAssistant, start_date: datetime, end_date: datetime - ) -> list[dict[str, Any]]: + ) -> list[CalendarEvent]: """Return calendar events within a datetime range.""" - events: list[dict[str, Any]] = [] + events: list[CalendarEvent] = [] for waste_type, waste_dates in self.coordinator.data.items(): events.extend( - { - "all_day": True, - "start": {"date": waste_date.isoformat()}, - "end": {"date": waste_date.isoformat()}, - "summary": WASTE_TYPE_TO_DESCRIPTION[waste_type], - } + CalendarEvent( + summary=WASTE_TYPE_TO_DESCRIPTION[waste_type], + start=waste_date, + end=waste_date, + ) for waste_date in waste_dates if start_date.date() <= waste_date <= end_date.date() ) @@ -86,12 +84,11 @@ class TwenteMilieuCalendar(TwenteMilieuEntity, CalendarEventDevice): self._event = None if next_waste_pickup_date is not None and next_waste_pickup_type is not None: - self._event = { - "all_day": True, - "start": {"date": next_waste_pickup_date.isoformat()}, - "end": {"date": next_waste_pickup_date.isoformat()}, - "summary": WASTE_TYPE_TO_DESCRIPTION[next_waste_pickup_type], - } + self._event = CalendarEvent( + summary=WASTE_TYPE_TO_DESCRIPTION[next_waste_pickup_type], + start=next_waste_pickup_date, + end=next_waste_pickup_date, + ) super()._handle_coordinator_update() diff --git a/homeassistant/components/twentemilieu/translations/bg.json b/homeassistant/components/twentemilieu/translations/bg.json index 8844b6124e0..6ddc25a367e 100644 --- a/homeassistant/components/twentemilieu/translations/bg.json +++ b/homeassistant/components/twentemilieu/translations/bg.json @@ -10,8 +10,7 @@ "house_number": "\u041d\u043e\u043c\u0435\u0440 \u043d\u0430 \u043a\u044a\u0449\u0430", "post_code": "\u041f\u043e\u0449\u0435\u043d\u0441\u043a\u0438 \u043a\u043e\u0434" }, - "description": "\u0421\u044a\u0437\u0434\u0430\u0439\u0442\u0435 Twente Milieu, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u044f\u0449\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0437\u0430 \u0441\u044a\u0431\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043e\u0442\u043f\u0430\u0434\u044a\u0446\u0438 \u043d\u0430 \u0432\u0430\u0448\u0438\u044f \u0430\u0434\u0440\u0435\u0441.", - "title": "Twente Milieu" + "description": "\u0421\u044a\u0437\u0434\u0430\u0439\u0442\u0435 Twente Milieu, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u044f\u0449\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0437\u0430 \u0441\u044a\u0431\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043e\u0442\u043f\u0430\u0434\u044a\u0446\u0438 \u043d\u0430 \u0432\u0430\u0448\u0438\u044f \u0430\u0434\u0440\u0435\u0441." } } } diff --git a/homeassistant/components/twentemilieu/translations/ca.json b/homeassistant/components/twentemilieu/translations/ca.json index 6c1469f16c1..c9a3b0bb6cb 100644 --- a/homeassistant/components/twentemilieu/translations/ca.json +++ b/homeassistant/components/twentemilieu/translations/ca.json @@ -14,8 +14,7 @@ "house_number": "N\u00famero de casa", "post_code": "Codi postal" }, - "description": "Configura Twente Milieu amb informaci\u00f3 de la recollida de residus a la teva adre\u00e7a.", - "title": "Twente Milieu" + "description": "Configura Twente Milieu amb informaci\u00f3 de la recollida de residus a la teva adre\u00e7a." } } } diff --git a/homeassistant/components/twentemilieu/translations/cs.json b/homeassistant/components/twentemilieu/translations/cs.json index 2eb6e267b2c..edcb6a22594 100644 --- a/homeassistant/components/twentemilieu/translations/cs.json +++ b/homeassistant/components/twentemilieu/translations/cs.json @@ -12,8 +12,7 @@ "house_letter": "Roz\u0161\u00ed\u0159en\u00ed ozna\u010den\u00ed \u010d\u00edsla domu", "house_number": "\u010c\u00edslo domu", "post_code": "PS\u010c" - }, - "title": "Twente Milieu" + } } } } diff --git a/homeassistant/components/twentemilieu/translations/da.json b/homeassistant/components/twentemilieu/translations/da.json index e1c9b6024ad..6930a37adb4 100644 --- a/homeassistant/components/twentemilieu/translations/da.json +++ b/homeassistant/components/twentemilieu/translations/da.json @@ -10,8 +10,7 @@ "house_number": "Husnummer", "post_code": "Postnummer" }, - "description": "Konfigurer Twente Milieu, der leverer oplysninger om indsamling af affald p\u00e5 din adresse.", - "title": "Twente Milieu" + "description": "Konfigurer Twente Milieu, der leverer oplysninger om indsamling af affald p\u00e5 din adresse." } } } diff --git a/homeassistant/components/twentemilieu/translations/de.json b/homeassistant/components/twentemilieu/translations/de.json index 4ce9ed23ea7..36ea2123bdb 100644 --- a/homeassistant/components/twentemilieu/translations/de.json +++ b/homeassistant/components/twentemilieu/translations/de.json @@ -14,8 +14,7 @@ "house_number": "Hausnummer", "post_code": "Postleitzahl" }, - "description": "Richte Twente Milieu mit Informationen zur Abfallsammlung unter deiner Adresse ein.", - "title": "Twente Milieu" + "description": "Richte Twente Milieu mit Informationen zur Abfallsammlung unter deiner Adresse ein." } } } diff --git a/homeassistant/components/twentemilieu/translations/el.json b/homeassistant/components/twentemilieu/translations/el.json index f4949d3832d..d8cdd9672bd 100644 --- a/homeassistant/components/twentemilieu/translations/el.json +++ b/homeassistant/components/twentemilieu/translations/el.json @@ -14,8 +14,7 @@ "house_number": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd", "post_code": "\u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b9\u03ba\u03cc\u03c2 \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2" }, - "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Twente Milieu \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03bb\u03bb\u03bf\u03b3\u03ae \u03b1\u03c0\u03bf\u03b2\u03bb\u03ae\u03c4\u03c9\u03bd \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03ae \u03c3\u03b1\u03c2.", - "title": "Twente Milieu" + "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Twente Milieu \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03bb\u03bb\u03bf\u03b3\u03ae \u03b1\u03c0\u03bf\u03b2\u03bb\u03ae\u03c4\u03c9\u03bd \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03ae \u03c3\u03b1\u03c2." } } } diff --git a/homeassistant/components/twentemilieu/translations/en.json b/homeassistant/components/twentemilieu/translations/en.json index fdc4023f89b..ac5f9e54b2e 100644 --- a/homeassistant/components/twentemilieu/translations/en.json +++ b/homeassistant/components/twentemilieu/translations/en.json @@ -14,8 +14,7 @@ "house_number": "House number", "post_code": "Postal code" }, - "description": "Set up Twente Milieu providing waste collection information on your address.", - "title": "Twente Milieu" + "description": "Set up Twente Milieu providing waste collection information on your address." } } } diff --git a/homeassistant/components/twentemilieu/translations/es-419.json b/homeassistant/components/twentemilieu/translations/es-419.json index 8adddffd407..7eb541978d3 100644 --- a/homeassistant/components/twentemilieu/translations/es-419.json +++ b/homeassistant/components/twentemilieu/translations/es-419.json @@ -10,8 +10,7 @@ "house_number": "N\u00famero de casa", "post_code": "C\u00f3digo postal" }, - "description": "Configure Twente Milieu proporcionando informaci\u00f3n de recolecci\u00f3n de residuos en su direcci\u00f3n.", - "title": "Twente Milieu" + "description": "Configure Twente Milieu proporcionando informaci\u00f3n de recolecci\u00f3n de residuos en su direcci\u00f3n." } } } diff --git a/homeassistant/components/twentemilieu/translations/es.json b/homeassistant/components/twentemilieu/translations/es.json index cf8410c55ad..259d202a9c3 100644 --- a/homeassistant/components/twentemilieu/translations/es.json +++ b/homeassistant/components/twentemilieu/translations/es.json @@ -14,8 +14,7 @@ "house_number": "N\u00famero de casa", "post_code": "C\u00f3digo postal" }, - "description": "Configure Twente Milieu proporcionando informaci\u00f3n sobre la recolecci\u00f3n de residuos en su direcci\u00f3n.", - "title": "Twente Milieu" + "description": "Configure Twente Milieu proporcionando informaci\u00f3n sobre la recolecci\u00f3n de residuos en su direcci\u00f3n." } } } diff --git a/homeassistant/components/twentemilieu/translations/et.json b/homeassistant/components/twentemilieu/translations/et.json index a77657d19ff..31c8ea6ee95 100644 --- a/homeassistant/components/twentemilieu/translations/et.json +++ b/homeassistant/components/twentemilieu/translations/et.json @@ -14,8 +14,7 @@ "house_number": "Maja number", "post_code": "Sihtnumber" }, - "description": "Seadista Twente Milieu, mis pakub j\u00e4\u00e4tmekogumist teie aadressil.", - "title": "" + "description": "Seadista Twente Milieu, mis pakub j\u00e4\u00e4tmekogumist teie aadressil." } } } diff --git a/homeassistant/components/twentemilieu/translations/fr.json b/homeassistant/components/twentemilieu/translations/fr.json index 6530216a5a4..560fcb89c29 100644 --- a/homeassistant/components/twentemilieu/translations/fr.json +++ b/homeassistant/components/twentemilieu/translations/fr.json @@ -14,8 +14,7 @@ "house_number": "Num\u00e9ro de maison", "post_code": "Code postal" }, - "description": "Configurez Twente Milieu en fournissant des informations sur la collecte des d\u00e9chets sur votre adresse.", - "title": "Twente Milieu" + "description": "Configurez Twente Milieu en fournissant des informations sur la collecte des d\u00e9chets sur votre adresse." } } } diff --git a/homeassistant/components/twentemilieu/translations/hu.json b/homeassistant/components/twentemilieu/translations/hu.json index 637dadb5baf..97a0cc8f022 100644 --- a/homeassistant/components/twentemilieu/translations/hu.json +++ b/homeassistant/components/twentemilieu/translations/hu.json @@ -14,8 +14,7 @@ "house_number": "h\u00e1zsz\u00e1m", "post_code": "ir\u00e1ny\u00edt\u00f3sz\u00e1m" }, - "description": "\u00c1ll\u00edtsa be a Twente Milieu szolg\u00e1ltat\u00e1st, amely hullad\u00e9kgy\u0171jt\u00e9si inform\u00e1ci\u00f3kat biztos\u00edt a c\u00edm\u00e9re.", - "title": "Twente Milieu" + "description": "\u00c1ll\u00edtsa be a Twente Milieu szolg\u00e1ltat\u00e1st, amely hullad\u00e9kgy\u0171jt\u00e9si inform\u00e1ci\u00f3kat biztos\u00edt a c\u00edm\u00e9re." } } } diff --git a/homeassistant/components/twentemilieu/translations/id.json b/homeassistant/components/twentemilieu/translations/id.json index 38746dfd12f..60148dc396c 100644 --- a/homeassistant/components/twentemilieu/translations/id.json +++ b/homeassistant/components/twentemilieu/translations/id.json @@ -14,8 +14,7 @@ "house_number": "Nomor rumah", "post_code": "Kode pos" }, - "description": "Siapkan Twente Milieu untuk memberikan informasi pengumpulan sampah di alamat Anda.", - "title": "Twente Milieu" + "description": "Siapkan Twente Milieu untuk memberikan informasi pengumpulan sampah di alamat Anda." } } } diff --git a/homeassistant/components/twentemilieu/translations/it.json b/homeassistant/components/twentemilieu/translations/it.json index a374885e7aa..7648d24b5e6 100644 --- a/homeassistant/components/twentemilieu/translations/it.json +++ b/homeassistant/components/twentemilieu/translations/it.json @@ -14,8 +14,7 @@ "house_number": "Numero civico", "post_code": "CAP" }, - "description": "Imposta Twente Milieu fornendo le informazioni sulla raccolta dei rifiuti al tuo indirizzo.", - "title": "Twente Milieu" + "description": "Imposta Twente Milieu fornendo le informazioni sulla raccolta dei rifiuti al tuo indirizzo." } } } diff --git a/homeassistant/components/twentemilieu/translations/ja.json b/homeassistant/components/twentemilieu/translations/ja.json index 8ec65b24fb5..5cd78fbba3c 100644 --- a/homeassistant/components/twentemilieu/translations/ja.json +++ b/homeassistant/components/twentemilieu/translations/ja.json @@ -14,8 +14,7 @@ "house_number": "\u5bb6\u5c4b\u756a\u53f7", "post_code": "\u90f5\u4fbf\u756a\u53f7" }, - "description": "\u3042\u306a\u305f\u306e\u4f4f\u6240\u306eTwente Milieu providing waste collection(\u30b4\u30df\u53ce\u96c6\u60c5\u5831)\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7", - "title": "Twente Milieu" + "description": "\u3042\u306a\u305f\u306e\u4f4f\u6240\u306eTwente Milieu providing waste collection(\u30b4\u30df\u53ce\u96c6\u60c5\u5831)\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" } } } diff --git a/homeassistant/components/twentemilieu/translations/ko.json b/homeassistant/components/twentemilieu/translations/ko.json index a27df565f1b..985650ff15b 100644 --- a/homeassistant/components/twentemilieu/translations/ko.json +++ b/homeassistant/components/twentemilieu/translations/ko.json @@ -14,8 +14,7 @@ "house_number": "\uc9d1 \ubc88\ud638", "post_code": "\uc6b0\ud3b8\ubc88\ud638" }, - "description": "\uc8fc\uc18c\uc5d0 \uc4f0\ub808\uae30 \uc218\uac70 \uc815\ubcf4\ub97c \ub123\uc5b4 Twente Milieu \ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "Twente Milieu" + "description": "\uc8fc\uc18c\uc5d0 \uc4f0\ub808\uae30 \uc218\uac70 \uc815\ubcf4\ub97c \ub123\uc5b4 Twente Milieu \ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/twentemilieu/translations/lb.json b/homeassistant/components/twentemilieu/translations/lb.json index 98454f62dbb..ca1591417e7 100644 --- a/homeassistant/components/twentemilieu/translations/lb.json +++ b/homeassistant/components/twentemilieu/translations/lb.json @@ -14,8 +14,7 @@ "house_number": "Haus Nummer", "post_code": "Postleitzuel" }, - "description": "Offallsammlung Informatiounen vun Twente Milieu zu \u00e4erer Adresse ariichten.", - "title": "Twente Milieu" + "description": "Offallsammlung Informatiounen vun Twente Milieu zu \u00e4erer Adresse ariichten." } } } diff --git a/homeassistant/components/twentemilieu/translations/nl.json b/homeassistant/components/twentemilieu/translations/nl.json index 54611aa9ab8..575c642a777 100644 --- a/homeassistant/components/twentemilieu/translations/nl.json +++ b/homeassistant/components/twentemilieu/translations/nl.json @@ -14,8 +14,7 @@ "house_number": "Huisnummer", "post_code": "Postcode" }, - "description": "Stel Twente Milieu in voor het inzamelen van afval op uw adres.", - "title": "Twente Milieu" + "description": "Stel Twente Milieu in voor het inzamelen van afval op uw adres." } } } diff --git a/homeassistant/components/twentemilieu/translations/nn.json b/homeassistant/components/twentemilieu/translations/nn.json deleted file mode 100644 index ed333bb9b51..00000000000 --- a/homeassistant/components/twentemilieu/translations/nn.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Twente Milieu" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/no.json b/homeassistant/components/twentemilieu/translations/no.json index ea7df786247..76a023e44e3 100644 --- a/homeassistant/components/twentemilieu/translations/no.json +++ b/homeassistant/components/twentemilieu/translations/no.json @@ -14,8 +14,7 @@ "house_number": "Husnummer", "post_code": "Postnummer" }, - "description": "Sett opp Twente Milieu som gir informasjon om innsamling av avfall p\u00e5 adressen din.", - "title": "" + "description": "Sett opp Twente Milieu som gir informasjon om innsamling av avfall p\u00e5 adressen din." } } } diff --git a/homeassistant/components/twentemilieu/translations/pl.json b/homeassistant/components/twentemilieu/translations/pl.json index 67f2d2b362b..d7f53bb9bf1 100644 --- a/homeassistant/components/twentemilieu/translations/pl.json +++ b/homeassistant/components/twentemilieu/translations/pl.json @@ -14,8 +14,7 @@ "house_number": "Numer domu", "post_code": "Kod pocztowy" }, - "description": "Skonfiguruj Twente Milieu, dostarczaj\u0105c informacji o zbieraniu odpad\u00f3w pod swoim adresem.", - "title": "Twente Milieu" + "description": "Skonfiguruj Twente Milieu, dostarczaj\u0105c informacji o zbieraniu odpad\u00f3w pod swoim adresem." } } } diff --git a/homeassistant/components/twentemilieu/translations/pt-BR.json b/homeassistant/components/twentemilieu/translations/pt-BR.json index 943b46849b6..8a7fa74b554 100644 --- a/homeassistant/components/twentemilieu/translations/pt-BR.json +++ b/homeassistant/components/twentemilieu/translations/pt-BR.json @@ -14,8 +14,7 @@ "house_number": "N\u00famero da casa", "post_code": "C\u00f3digo postal" }, - "description": "Configure o Twente Milieu, fornecendo informa\u00e7\u00f5es de coleta de lixo em seu endere\u00e7o.", - "title": "Twente Milieu" + "description": "Configure o Twente Milieu, fornecendo informa\u00e7\u00f5es de coleta de lixo em seu endere\u00e7o." } } } diff --git a/homeassistant/components/twentemilieu/translations/ru.json b/homeassistant/components/twentemilieu/translations/ru.json index f7da9b628fd..33748e9d16b 100644 --- a/homeassistant/components/twentemilieu/translations/ru.json +++ b/homeassistant/components/twentemilieu/translations/ru.json @@ -14,8 +14,7 @@ "house_number": "\u041d\u043e\u043c\u0435\u0440 \u0434\u043e\u043c\u0430", "post_code": "\u041f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Twente Milieu \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0432\u044b\u0432\u043e\u0437\u0435 \u043c\u0443\u0441\u043e\u0440\u0430 \u043f\u043e \u0412\u0430\u0448\u0435\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443.", - "title": "Twente Milieu" + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Twente Milieu \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0432\u044b\u0432\u043e\u0437\u0435 \u043c\u0443\u0441\u043e\u0440\u0430 \u043f\u043e \u0412\u0430\u0448\u0435\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443." } } } diff --git a/homeassistant/components/twentemilieu/translations/sl.json b/homeassistant/components/twentemilieu/translations/sl.json index affe8269613..313a5ae5a87 100644 --- a/homeassistant/components/twentemilieu/translations/sl.json +++ b/homeassistant/components/twentemilieu/translations/sl.json @@ -10,8 +10,7 @@ "house_number": "Hi\u0161na \u0161tevilka", "post_code": "Po\u0161tna \u0161tevilka" }, - "description": "Nastavite Twente milieu, ki zagotavlja informacije o zbiranju odpadkov na va\u0161em naslovu.", - "title": "Twente Milieu" + "description": "Nastavite Twente milieu, ki zagotavlja informacije o zbiranju odpadkov na va\u0161em naslovu." } } } diff --git a/homeassistant/components/twentemilieu/translations/sv.json b/homeassistant/components/twentemilieu/translations/sv.json index fa1481f7e10..4e8bb592d05 100644 --- a/homeassistant/components/twentemilieu/translations/sv.json +++ b/homeassistant/components/twentemilieu/translations/sv.json @@ -10,8 +10,7 @@ "house_number": "Husnummer", "post_code": "Postnummer" }, - "description": "St\u00e4ll in Twente Milieu som ger information om avfallshantering p\u00e5 din adress.", - "title": "Twente Milieu" + "description": "St\u00e4ll in Twente Milieu som ger information om avfallshantering p\u00e5 din adress." } } } diff --git a/homeassistant/components/twentemilieu/translations/tr.json b/homeassistant/components/twentemilieu/translations/tr.json index 363128d9c1e..9f03d2c9189 100644 --- a/homeassistant/components/twentemilieu/translations/tr.json +++ b/homeassistant/components/twentemilieu/translations/tr.json @@ -14,8 +14,7 @@ "house_number": "Ev numaras\u0131", "post_code": "Posta kodu" }, - "description": "Adresinizde at\u0131k toplama bilgileri sa\u011flayan Twente Milieu'yu kurun.", - "title": "Twente Milieu" + "description": "Adresinizde at\u0131k toplama bilgileri sa\u011flayan Twente Milieu'yu kurun." } } } diff --git a/homeassistant/components/twentemilieu/translations/uk.json b/homeassistant/components/twentemilieu/translations/uk.json index 435bd79fb85..5fa562a9afa 100644 --- a/homeassistant/components/twentemilieu/translations/uk.json +++ b/homeassistant/components/twentemilieu/translations/uk.json @@ -14,8 +14,7 @@ "house_number": "\u041d\u043e\u043c\u0435\u0440 \u0431\u0443\u0434\u0438\u043d\u043a\u0443", "post_code": "\u041f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441" }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Twente Milieu \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u0432\u0438\u0432\u0435\u0437\u0435\u043d\u043d\u044f \u0441\u043c\u0456\u0442\u0442\u044f \u0437\u0430 \u0412\u0430\u0448\u043e\u044e \u0430\u0434\u0440\u0435\u0441\u043e\u044e.", - "title": "Twente Milieu" + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Twente Milieu \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e \u0432\u0438\u0432\u0435\u0437\u0435\u043d\u043d\u044f \u0441\u043c\u0456\u0442\u0442\u044f \u0437\u0430 \u0412\u0430\u0448\u043e\u044e \u0430\u0434\u0440\u0435\u0441\u043e\u044e." } } } diff --git a/homeassistant/components/twentemilieu/translations/zh-Hant.json b/homeassistant/components/twentemilieu/translations/zh-Hant.json index 11cf62d2d60..51373552418 100644 --- a/homeassistant/components/twentemilieu/translations/zh-Hant.json +++ b/homeassistant/components/twentemilieu/translations/zh-Hant.json @@ -14,8 +14,7 @@ "house_number": "\u9580\u724c\u865f\u78bc", "post_code": "\u90f5\u905e\u5340\u865f" }, - "description": "\u8a2d\u5b9a Twente Milieu \u4ee5\u53d6\u5f97\u8a72\u5730\u5740\u5ee2\u68c4\u7269\u56de\u6536\u8cc7\u8a0a\u3002", - "title": "Twente Milieu" + "description": "\u8a2d\u5b9a Twente Milieu \u4ee5\u53d6\u5f97\u8a72\u5730\u5740\u5ee2\u68c4\u7269\u56de\u6536\u8cc7\u8a0a\u3002" } } } diff --git a/homeassistant/components/twilio/translations/cs.json b/homeassistant/components/twilio/translations/cs.json index f45c4bf881e..8572109edcd 100644 --- a/homeassistant/components/twilio/translations/cs.json +++ b/homeassistant/components/twilio/translations/cs.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "cloud_not_connected": "Nen\u00ed p\u0159ipojeno k Home Assistant Cloud.", "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace.", "webhook_not_internet_accessible": "V\u00e1\u0161 Home Assistant mus\u00ed b\u00fdt p\u0159\u00edstupn\u00fd z internetu, aby mohl p\u0159ij\u00edmat zpr\u00e1vy webhook." }, diff --git a/homeassistant/components/twilio/translations/id.json b/homeassistant/components/twilio/translations/id.json index 06a77bc974e..6c447e22f63 100644 --- a/homeassistant/components/twilio/translations/id.json +++ b/homeassistant/components/twilio/translations/id.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Instans Home Assistant Anda harus dapat diakses dari internet untuk menerima pesan webhook." }, "create_entry": { - "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [Webhooks dengan Twilio]({twilio_url}).\n\nIsikan info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content-Type: application/x-www-form-urlencoded\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [Webhooks dengan Twilio]({twilio_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content-Type: application/x-www-form-urlencoded\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." }, "step": { "user": { diff --git a/homeassistant/components/twilio/translations/ja.json b/homeassistant/components/twilio/translations/ja.json index 84ea72878e7..521fee184f2 100644 --- a/homeassistant/components/twilio/translations/ja.json +++ b/homeassistant/components/twilio/translations/ja.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Webhook\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u53d7\u4fe1\u3059\u308b\u306b\u306f\u3001Home Assistant\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u3001\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u304b\u3089\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002" }, "create_entry": { - "default": "Home Assistant\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u3059\u308b\u306b\u306f\u3001[Webhooks with Twilio]({twilio_url})\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u4ee5\u4e0b\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044:\n\n- URL: `{webhook_url}`\n- Method(\u65b9\u5f0f): POST\n- Content Type: application/x-www-form-urlencoded\n\n\u53d7\u4fe1\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8]({docs_url})\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" + "default": "Home Assistant\u306b\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u3059\u308b\u306b\u306f\u3001[Webhooks with Twilio]({twilio_url})\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u4ee5\u4e0b\u306e\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044:\n\n- URL: `{webhook_url}`\n- Method(\u65b9\u5f0f): POST\n- Content Type(\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u7a2e\u985e): application/x-www-form-urlencoded\n\n\u53d7\u4fe1\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u30aa\u30fc\u30c8\u30e1\u30fc\u30b7\u30e7\u30f3\u3092\u8a2d\u5b9a\u3059\u308b\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001[\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8]({docs_url})\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002" }, "step": { "user": { diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 440dab9a6ff..9068c04e01b 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -12,9 +12,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_RGBW_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -70,16 +68,16 @@ class TwinklyLight(LightEntity): self._conf = conf if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW: - self._attr_supported_color_modes = {COLOR_MODE_RGBW} - self._attr_color_mode = COLOR_MODE_RGBW + self._attr_supported_color_modes = {ColorMode.RGBW} + self._attr_color_mode = ColorMode.RGBW self._attr_rgbw_color = (255, 255, 255, 0) elif device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGB: - self._attr_supported_color_modes = {COLOR_MODE_RGB} - self._attr_color_mode = COLOR_MODE_RGB + self._attr_supported_color_modes = {ColorMode.RGB} + self._attr_color_mode = ColorMode.RGB self._attr_rgb_color = (255, 255, 255) else: - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + self._attr_color_mode = ColorMode.BRIGHTNESS # Those are saved in the config entry in order to have meaningful values even # if the device is currently offline. diff --git a/homeassistant/components/twinkly/translations/ca.json b/homeassistant/components/twinkly/translations/ca.json index ecf2c4ed12c..4813f7736f9 100644 --- a/homeassistant/components/twinkly/translations/ca.json +++ b/homeassistant/components/twinkly/translations/ca.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Amfitri\u00f3" - }, - "description": "Configura la teva tira LED de Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/cs.json b/homeassistant/components/twinkly/translations/cs.json index be5b291744b..26afb737dce 100644 --- a/homeassistant/components/twinkly/translations/cs.json +++ b/homeassistant/components/twinkly/translations/cs.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Hostitel" - }, - "description": "Nastavte sv\u016fj LED p\u00e1sek Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/de.json b/homeassistant/components/twinkly/translations/de.json index d31b243f7b9..5d096c5b594 100644 --- a/homeassistant/components/twinkly/translations/de.json +++ b/homeassistant/components/twinkly/translations/de.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host" - }, - "description": "Einrichten deiner Twinkly-Led-Kette", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/el.json b/homeassistant/components/twinkly/translations/el.json index 8e0294b87b5..1210c6dc76a 100644 --- a/homeassistant/components/twinkly/translations/el.json +++ b/homeassistant/components/twinkly/translations/el.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" - }, - "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf Twinkly led string \u03c3\u03b1\u03c2", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/en.json b/homeassistant/components/twinkly/translations/en.json index fbb63fbc3e8..720a258161f 100644 --- a/homeassistant/components/twinkly/translations/en.json +++ b/homeassistant/components/twinkly/translations/en.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host" - }, - "description": "Set up your Twinkly led string", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/es.json b/homeassistant/components/twinkly/translations/es.json index 691c0afcda6..e18d54adb9e 100644 --- a/homeassistant/components/twinkly/translations/es.json +++ b/homeassistant/components/twinkly/translations/es.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host (o direcci\u00f3n IP) de tu dispositivo Twinkly" - }, - "description": "Configura tu tira led Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/et.json b/homeassistant/components/twinkly/translations/et.json index bdb4ef3772c..bf902984d26 100644 --- a/homeassistant/components/twinkly/translations/et.json +++ b/homeassistant/components/twinkly/translations/et.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host" - }, - "description": "Seadista oma Twinkly LED riba", - "title": "" + } } } } diff --git a/homeassistant/components/twinkly/translations/fr.json b/homeassistant/components/twinkly/translations/fr.json index c5b01400457..cf6fe97ce88 100644 --- a/homeassistant/components/twinkly/translations/fr.json +++ b/homeassistant/components/twinkly/translations/fr.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "H\u00f4te" - }, - "description": "Configurer votre Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/hu.json b/homeassistant/components/twinkly/translations/hu.json index 94983ce4893..0ad6208ca16 100644 --- a/homeassistant/components/twinkly/translations/hu.json +++ b/homeassistant/components/twinkly/translations/hu.json @@ -12,10 +12,8 @@ }, "user": { "data": { - "host": "A Twinkly eszk\u00f6z c\u00edme" - }, - "description": "\u00c1ll\u00edtsa be a Twinkly led-karakterl\u00e1nc\u00e1t", - "title": "Twinkly" + "host": "C\u00edm" + } } } } diff --git a/homeassistant/components/twinkly/translations/id.json b/homeassistant/components/twinkly/translations/id.json index 330bcf6ce11..b37e20bf023 100644 --- a/homeassistant/components/twinkly/translations/id.json +++ b/homeassistant/components/twinkly/translations/id.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host" - }, - "description": "Siapkan string led Twinkly Anda", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/it.json b/homeassistant/components/twinkly/translations/it.json index a64ecaeb425..fec569379d4 100644 --- a/homeassistant/components/twinkly/translations/it.json +++ b/homeassistant/components/twinkly/translations/it.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host" - }, - "description": "Configura la tua stringa led Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/ja.json b/homeassistant/components/twinkly/translations/ja.json index e3955e4286f..2ec0d28e0bf 100644 --- a/homeassistant/components/twinkly/translations/ja.json +++ b/homeassistant/components/twinkly/translations/ja.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Twinkly device\u306e\u30db\u30b9\u30c8(\u307e\u305f\u306fIP\u30a2\u30c9\u30ec\u30b9)" - }, - "description": "Twinkly led string\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/ka.json b/homeassistant/components/twinkly/translations/ka.json index d0d6b61f4cc..293552c37d4 100644 --- a/homeassistant/components/twinkly/translations/ka.json +++ b/homeassistant/components/twinkly/translations/ka.json @@ -10,9 +10,7 @@ "user": { "data": { "host": "\u10d7\u10e5\u10d5\u10d4\u10dc\u10d8 twinkly \u10db\u10dd\u10ec\u10e7\u10dd\u10d1\u10d8\u10da\u10dd\u10d1\u10d8\u10e1 \u10f0\u10dd\u10e1\u10e2\u10d8 (\u10d0\u10dc IP \u10db\u10d8\u10e1\u10d0\u10db\u10d0\u10e0\u10d7\u10d8)" - }, - "description": "\u10d7\u10e5\u10d5\u10d4\u10dc\u10d8 Twinkly \u10e8\u10e3\u10e5\u10d3\u10d8\u10dd\u10d3\u10d8\u10e1 \u10da\u10d4\u10dc\u10e2\u10d8\u10e1 \u10d3\u10d0\u10e7\u10d4\u10dc\u10d4\u10d1\u10d0", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/ko.json b/homeassistant/components/twinkly/translations/ko.json index b3b23991e3d..257be47f610 100644 --- a/homeassistant/components/twinkly/translations/ko.json +++ b/homeassistant/components/twinkly/translations/ko.json @@ -10,9 +10,7 @@ "user": { "data": { "host": "Twinkly \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 (\ub610\ub294 IP \uc8fc\uc18c)" - }, - "description": "Twinkly LED \uc904 \uc870\uba85 \uc124\uc815\ud558\uae30", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/nl.json b/homeassistant/components/twinkly/translations/nl.json index 38331177895..b41baa0b403 100644 --- a/homeassistant/components/twinkly/translations/nl.json +++ b/homeassistant/components/twinkly/translations/nl.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Host" - }, - "description": "Uw Twinkly LED-string instellen", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/no.json b/homeassistant/components/twinkly/translations/no.json index af3b74a9686..72f88054aa7 100644 --- a/homeassistant/components/twinkly/translations/no.json +++ b/homeassistant/components/twinkly/translations/no.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Vert" - }, - "description": "Sett opp Twinkly-led-strengen", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/pl.json b/homeassistant/components/twinkly/translations/pl.json index 8036d6cc2f6..a9538177101 100644 --- a/homeassistant/components/twinkly/translations/pl.json +++ b/homeassistant/components/twinkly/translations/pl.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Nazwa hosta lub adres IP" - }, - "description": "Konfiguracja Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/pt-BR.json b/homeassistant/components/twinkly/translations/pt-BR.json index 3aff4eb867d..789802f44e9 100644 --- a/homeassistant/components/twinkly/translations/pt-BR.json +++ b/homeassistant/components/twinkly/translations/pt-BR.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Nome do host" - }, - "description": "Configure sua fita de led Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/ru.json b/homeassistant/components/twinkly/translations/ru.json index a7e0ff145cd..d1e06eee9cc 100644 --- a/homeassistant/components/twinkly/translations/ru.json +++ b/homeassistant/components/twinkly/translations/ru.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "\u0425\u043e\u0441\u0442" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441\u0432\u0435\u0442\u043e\u0434\u0438\u043e\u0434\u043d\u043e\u0439 \u043b\u0435\u043d\u0442\u044b Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/tr.json b/homeassistant/components/twinkly/translations/tr.json index 0e78648d811..39bf9b32e55 100644 --- a/homeassistant/components/twinkly/translations/tr.json +++ b/homeassistant/components/twinkly/translations/tr.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "Sunucu" - }, - "description": "Twinkly led dizinizi ayarlay\u0131n", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/uk.json b/homeassistant/components/twinkly/translations/uk.json index bd256d31b03..8f3d6d0cbaa 100644 --- a/homeassistant/components/twinkly/translations/uk.json +++ b/homeassistant/components/twinkly/translations/uk.json @@ -10,9 +10,7 @@ "user": { "data": { "host": "\u0406\u043c'\u044f \u0445\u043e\u0441\u0442\u0430 (\u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430) \u0412\u0430\u0448\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Twinkly" - }, - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0432\u0456\u0442\u043b\u043e\u0434\u0456\u043e\u0434\u043d\u043e\u0457 \u0441\u0442\u0440\u0456\u0447\u043a\u0438 Twinkly", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twinkly/translations/zh-Hant.json b/homeassistant/components/twinkly/translations/zh-Hant.json index 77e0a69cf65..d016c286034 100644 --- a/homeassistant/components/twinkly/translations/zh-Hant.json +++ b/homeassistant/components/twinkly/translations/zh-Hant.json @@ -13,9 +13,7 @@ "user": { "data": { "host": "\u4e3b\u6a5f\u7aef" - }, - "description": "\u8a2d\u5b9a Twinkly LED \u71c8\u4e32", - "title": "Twinkly" + } } } } diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json index 4e80eef6021..0a962b39d7a 100644 --- a/homeassistant/components/twitter/manifest.json +++ b/homeassistant/components/twitter/manifest.json @@ -2,7 +2,7 @@ "domain": "twitter", "name": "Twitter", "documentation": "https://www.home-assistant.io/integrations/twitter", - "requirements": ["TwitterAPI==2.7.5"], + "requirements": ["TwitterAPI==2.7.12"], "codeowners": [], "iot_class": "cloud_push", "loggers": ["TwitterAPI"] diff --git a/homeassistant/components/ue_smart_radio/media_player.py b/homeassistant/components/ue_smart_radio/media_player.py index d04d96dcf2e..f7e0621b5e0 100644 --- a/homeassistant/components/ue_smart_radio/media_player.py +++ b/homeassistant/components/ue_smart_radio/media_player.py @@ -6,19 +6,12 @@ import logging import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, @@ -37,18 +30,6 @@ _LOGGER = logging.getLogger(__name__) ICON = "mdi:radio" URL = "http://decibel.logitechmusic.com/jsonrpc.js" -SUPPORT_UE_SMART_RADIO = ( - SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE -) - PLAYBACK_DICT = {"play": STATE_PLAYING, "pause": STATE_PAUSED, "stop": STATE_IDLE} PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -103,6 +84,18 @@ def setup_platform( class UERadioDevice(MediaPlayerEntity): """Representation of a Logitech UE Smart Radio device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + ) + def __init__(self, session, player_id, player_name): """Initialize the Logitech UE Smart Radio device.""" self._session = session @@ -179,11 +172,6 @@ class UERadioDevice(MediaPlayerEntity): """Volume level of the media player (0..1).""" return self._volume - @property - def supported_features(self): - """Flag of features that are supported.""" - return SUPPORT_UE_SMART_RADIO - @property def media_content_type(self): """Return the media content type.""" diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 6e40798fb7a..d02f3f49a5e 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -256,6 +256,8 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Manage the UniFi Network options.""" + if self.config_entry.entry_id not in self.hass.data[UNIFI_DOMAIN]: + return self.async_abort(reason="integration_not_setup") self.controller = self.hass.data[UNIFI_DOMAIN][self.config_entry.entry_id] self.options[CONF_BLOCK_CLIENT] = self.controller.option_block_clients diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 60ea4b3284b..2ae8eb2e32b 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -1,5 +1,7 @@ """Track both clients and devices using UniFi Network.""" + from datetime import timedelta +import logging from aiounifi.api import SOURCE_DATA from aiounifi.events import ( @@ -28,6 +30,8 @@ from .const import DOMAIN as UNIFI_DOMAIN from .unifi_client import UniFiClient from .unifi_entity_base import UniFiBase +LOGGER = logging.getLogger(__name__) + CLIENT_TRACKER = "client" DEVICE_TRACKER = "device" @@ -103,11 +107,11 @@ def add_client_entities(controller, async_add_entities, clients): trackers = [] for mac in clients: - if mac in controller.entities[DOMAIN][UniFiClientTracker.TYPE]: + if mac in controller.entities[DOMAIN][UniFiClientTracker.TYPE] or not ( + client := controller.api.clients.get(mac) + ): continue - client = controller.api.clients[mac] - if mac not in controller.wireless_clients: if not controller.option_track_wired_clients: continue @@ -150,16 +154,33 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): """Set up tracked client.""" super().__init__(client, controller) - self.heartbeat_check = False self._controller_connection_state_changed = False - self._last_seen = client.last_seen or 0 + last_seen = client.last_seen or 0 self.schedule_update = self._is_connected = ( self.is_wired == client.is_wired - and dt_util.utcnow() - dt_util.utc_from_timestamp(float(self._last_seen)) + and dt_util.utcnow() - dt_util.utc_from_timestamp(float(last_seen)) < controller.option_detection_time ) + @callback + def _async_log_debug_data(self, method: str) -> None: + """Print debug data about entity.""" + if not LOGGER.isEnabledFor(logging.DEBUG): + return + last_seen = self.client.last_seen or 0 + LOGGER.debug( + "%s [%s, %s] [%s %s] [%s] %s (%s)", + method, + self.entity_id, + self.client.mac, + self.schedule_update, + self._is_connected, + dt_util.utc_from_timestamp(float(last_seen)), + dt_util.utcnow() - dt_util.utc_from_timestamp(float(last_seen)), + last_seen, + ) + async def async_added_to_hass(self) -> None: """Watch object when added.""" self.async_on_remove( @@ -170,6 +191,7 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): ) ) await super().async_added_to_hass() + self._async_log_debug_data("added_to_hass") async def async_will_remove_from_hass(self) -> None: """Disconnect object when removed.""" @@ -200,16 +222,16 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): self.client.last_updated == SOURCE_DATA and self.is_wired == self.client.is_wired ): - self._last_seen = self.client.last_seen self._is_connected = True self.schedule_update = True + self._async_log_debug_data("update_callback") + if self.schedule_update: self.schedule_update = False self.controller.async_heartbeat( self.unique_id, dt_util.utcnow() + self.controller.option_detection_time ) - self.heartbeat_check = True super().async_update_callback() @@ -218,6 +240,7 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): """No heart beat by device.""" self._is_connected = False self.async_write_ha_state() + self._async_log_debug_data("make_disconnected") @property def device_info(self) -> None: diff --git a/homeassistant/components/unifi/strings.json b/homeassistant/components/unifi/strings.json index 476a0bfdd61..8d6df90b704 100644 --- a/homeassistant/components/unifi/strings.json +++ b/homeassistant/components/unifi/strings.json @@ -21,11 +21,14 @@ }, "abort": { "already_configured": "UniFi Network site is already configured", - "configuration_updated": "Configuration updated.", + "configuration_updated": "Configuration updated", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "options": { + "abort": { + "integration_not_setup": "UniFi integration is not setup" + }, "step": { "device_tracker": { "data": { diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 5d08c8b816d..2a1c17cb6e3 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -26,6 +26,9 @@ } }, "options": { + "abort": { + "integration_not_setup": "UniFi integration is not setup" + }, "step": { "client_control": { "data": { diff --git a/homeassistant/components/unifi/translations/hu.json b/homeassistant/components/unifi/translations/hu.json index 7692342bf89..ea4c7484d44 100644 --- a/homeassistant/components/unifi/translations/hu.json +++ b/homeassistant/components/unifi/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A vez\u00e9rl\u0151 webhelye m\u00e1r konfigur\u00e1lva van", + "already_configured": "Az UniFi Network webhely m\u00e1r konfigur\u00e1lva van", "configuration_updated": "A konfigur\u00e1ci\u00f3 friss\u00edtve.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." }, @@ -39,7 +39,7 @@ "device_tracker": { "data": { "detection_time": "Id\u0151 m\u00e1sodpercben az utols\u00f3 l\u00e1t\u00e1st\u00f3l a t\u00e1vol tart\u00e1sig", - "ignore_wired_bug": "Az UniFi vezet\u00e9kes hibalogika letilt\u00e1sa", + "ignore_wired_bug": "Az UniFi Network vezet\u00e9kes hibalogika letilt\u00e1sa", "ssid_filter": "V\u00e1lassza ki az SSID -ket a vezet\u00e9k n\u00e9lk\u00fcli \u00fcgyfelek nyomon k\u00f6vet\u00e9s\u00e9hez", "track_clients": "K\u00f6vesse nyomon a h\u00e1l\u00f3zati \u00fcgyfeleket", "track_devices": "H\u00e1l\u00f3zati eszk\u00f6z\u00f6k nyomon k\u00f6vet\u00e9se (Ubiquiti eszk\u00f6z\u00f6k)", diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index eeda8a8c9bb..7ddc0fde4fe 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -50,8 +50,8 @@ }, "init": { "data": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" } }, "simple_options": { diff --git a/homeassistant/components/unifiled/light.py b/homeassistant/components/unifiled/light.py index 0e08d282806..dd5a5b5290a 100644 --- a/homeassistant/components/unifiled/light.py +++ b/homeassistant/components/unifiled/light.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME @@ -59,6 +59,9 @@ def setup_platform( class UnifiLedLight(LightEntity): """Representation of an unifiled Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, light, api): """Init Unifi LED Light.""" @@ -69,7 +72,6 @@ class UnifiLedLight(LightEntity): self._state = light["status"]["output"] self._available = light["isOnline"] self._brightness = self._api.convertfrom100to255(light["status"]["led"]) - self._features = SUPPORT_BRIGHTNESS @property def name(self): @@ -96,11 +98,6 @@ class UnifiLedLight(LightEntity): """Return true if light is on.""" return self._state - @property - def supported_features(self): - """Return the supported features of this light.""" - return self._features - def turn_on(self, **kwargs): """Instruct the light to turn on.""" self._api.setdevicebrightness( diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index de7d4107653..928d82f317b 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -8,7 +8,7 @@ from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.data import Camera as UFPCamera, StateType from pyunifiprotect.data.devices import CameraChannel -from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -134,7 +134,7 @@ class ProtectCamera(ProtectDeviceEntity, Camera): None if disable_stream else rtsp_url ) self._attr_supported_features: int = ( - SUPPORT_STREAM if self._stream_source else 0 + CameraEntityFeature.STREAM if self._stream_source else 0 ) @callback diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index bd66b5f086a..0cfce44a6ca 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -259,6 +259,10 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await self.hass.config_entries.async_reload(self.entry.entry_id) return self.async_abort(reason="reauth_successful") + self.context["title_placeholders"] = { + "name": self.entry.title, + "ip_address": self.entry.data[CONF_HOST], + } return self.async_show_form( step_id="reauth_confirm", data_schema=vol.Schema( diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index 8e2adf82043..8a6f2f5a371 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -6,11 +6,7 @@ from typing import Any from pyunifiprotect.data import Light -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -59,7 +55,8 @@ class ProtectLight(ProtectDeviceEntity, LightEntity): device: Light _attr_icon = "mdi:spotlight-beam" - _attr_supported_features = SUPPORT_BRIGHTNESS + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @callback def _async_update_device_from_protect(self) -> None: diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 4de59c8252a..0b7c2a2f60d 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -13,18 +13,12 @@ from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, MediaPlayerEntityDescription, + MediaPlayerEntityFeature, ) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_BROWSE_MEDIA, - SUPPORT_PLAY_MEDIA, - SUPPORT_STOP, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PLAYING from homeassistant.core import HomeAssistant, callback @@ -63,6 +57,13 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity): device: Camera entity_description: MediaPlayerEntityDescription + _attr_supported_features = ( + MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) def __init__( self, @@ -79,13 +80,6 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity): ) self._attr_name = f"{self.device.name} Speaker" - self._attr_supported_features = ( - SUPPORT_PLAY_MEDIA - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP - | SUPPORT_STOP - | SUPPORT_BROWSE_MEDIA - ) self._attr_media_content_type = MEDIA_TYPE_MUSIC @callback diff --git a/homeassistant/components/unifiprotect/models.py b/homeassistant/components/unifiprotect/models.py index ecc3e4210ff..c28e1757722 100644 --- a/homeassistant/components/unifiprotect/models.py +++ b/homeassistant/components/unifiprotect/models.py @@ -46,7 +46,7 @@ class ProtectRequiredKeysMixin(EntityDescription, Generic[T]): @dataclass -class ProtectSetableKeysMixin(ProtectRequiredKeysMixin, Generic[T]): +class ProtectSetableKeysMixin(ProtectRequiredKeysMixin[T]): """Mixin for settable values.""" ufp_set_method: str | None = None diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 9d0a2bd69bd..d6dfba0c38f 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -3,7 +3,6 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta -from typing import Generic from pyunifiprotect.data.devices import Camera, Doorlock, Light @@ -31,7 +30,7 @@ class NumberKeysMixin: @dataclass class ProtectNumberEntityDescription( - ProtectSetableKeysMixin, NumberEntityDescription, NumberKeysMixin, Generic[T] + ProtectSetableKeysMixin[T], NumberEntityDescription, NumberKeysMixin ): """Describes UniFi Protect Number entity.""" diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index dd2e6051c2b..b7b53ff81c8 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from datetime import timedelta from enum import Enum import logging -from typing import Any, Final, Generic +from typing import Any, Final from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.data import ( @@ -104,7 +104,7 @@ SET_DOORBELL_LCD_MESSAGE_SCHEMA = vol.Schema( @dataclass class ProtectSelectEntityDescription( - ProtectSetableKeysMixin, SelectEntityDescription, Generic[T] + ProtectSetableKeysMixin[T], SelectEntityDescription ): """Describes UniFi Protect Select entity.""" diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 00ac1304d88..c3b49fbdf9d 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import datetime import logging -from typing import Any, Generic +from typing import Any from pyunifiprotect.data import ( NVR, @@ -54,13 +54,13 @@ DEVICE_CLASS_DETECTION = "unifiprotect__detection" @dataclass class ProtectSensorEntityDescription( - ProtectRequiredKeysMixin, SensorEntityDescription, Generic[T] + ProtectRequiredKeysMixin[T], SensorEntityDescription ): """Describes UniFi Protect Sensor entity.""" precision: int | None = None - def get_ufp_value(self, obj: ProtectDeviceModel) -> Any: + def get_ufp_value(self, obj: T) -> Any: """Return value from UniFi Protect device.""" value = super().get_ufp_value(obj) diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index a3626c3082e..2257f399f75 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass import logging -from typing import Any, Generic +from typing import Any from pyunifiprotect.data import Camera, RecordingMode, VideoMode from pyunifiprotect.data.base import ProtectAdoptableDeviceModel @@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) @dataclass class ProtectSwitchEntityDescription( - ProtectSetableKeysMixin, SwitchEntityDescription, Generic[T] + ProtectSetableKeysMixin[T], SwitchEntityDescription ): """Describes UniFi Protect Switch entity.""" diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 0c90b333a4b..e29a18f285f 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -9,6 +9,7 @@ from homeassistant.components.media_player import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.components.media_player.const import ( ATTR_APP_ID, @@ -42,22 +43,6 @@ from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -457,57 +442,57 @@ class UniversalMediaPlayer(MediaPlayerEntity): flags = self._child_attr(ATTR_SUPPORTED_FEATURES) or 0 if SERVICE_TURN_ON in self._cmds: - flags |= SUPPORT_TURN_ON + flags |= MediaPlayerEntityFeature.TURN_ON if SERVICE_TURN_OFF in self._cmds: - flags |= SUPPORT_TURN_OFF + flags |= MediaPlayerEntityFeature.TURN_OFF if SERVICE_MEDIA_PLAY_PAUSE in self._cmds: - flags |= SUPPORT_PLAY | SUPPORT_PAUSE + flags |= MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE else: if SERVICE_MEDIA_PLAY in self._cmds: - flags |= SUPPORT_PLAY + flags |= MediaPlayerEntityFeature.PLAY if SERVICE_MEDIA_PAUSE in self._cmds: - flags |= SUPPORT_PAUSE + flags |= MediaPlayerEntityFeature.PAUSE if SERVICE_MEDIA_STOP in self._cmds: - flags |= SUPPORT_STOP + flags |= MediaPlayerEntityFeature.STOP if SERVICE_MEDIA_NEXT_TRACK in self._cmds: - flags |= SUPPORT_NEXT_TRACK + flags |= MediaPlayerEntityFeature.NEXT_TRACK if SERVICE_MEDIA_PREVIOUS_TRACK in self._cmds: - flags |= SUPPORT_PREVIOUS_TRACK + flags |= MediaPlayerEntityFeature.PREVIOUS_TRACK if any(cmd in self._cmds for cmd in (SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN)): - flags |= SUPPORT_VOLUME_STEP + flags |= MediaPlayerEntityFeature.VOLUME_STEP if SERVICE_VOLUME_SET in self._cmds: - flags |= SUPPORT_VOLUME_SET + flags |= MediaPlayerEntityFeature.VOLUME_SET if SERVICE_VOLUME_MUTE in self._cmds and ATTR_MEDIA_VOLUME_MUTED in self._attrs: - flags |= SUPPORT_VOLUME_MUTE + flags |= MediaPlayerEntityFeature.VOLUME_MUTE if ( SERVICE_SELECT_SOURCE in self._cmds and ATTR_INPUT_SOURCE_LIST in self._attrs ): - flags |= SUPPORT_SELECT_SOURCE + flags |= MediaPlayerEntityFeature.SELECT_SOURCE if SERVICE_PLAY_MEDIA in self._cmds: - flags |= SUPPORT_PLAY_MEDIA + flags |= MediaPlayerEntityFeature.PLAY_MEDIA if SERVICE_CLEAR_PLAYLIST in self._cmds: - flags |= SUPPORT_CLEAR_PLAYLIST + flags |= MediaPlayerEntityFeature.CLEAR_PLAYLIST if SERVICE_SHUFFLE_SET in self._cmds and ATTR_MEDIA_SHUFFLE in self._attrs: - flags |= SUPPORT_SHUFFLE_SET + flags |= MediaPlayerEntityFeature.SHUFFLE_SET if SERVICE_REPEAT_SET in self._cmds and ATTR_MEDIA_REPEAT in self._attrs: - flags |= SUPPORT_REPEAT_SET + flags |= MediaPlayerEntityFeature.REPEAT_SET if ( SERVICE_SELECT_SOUND_MODE in self._cmds and ATTR_SOUND_MODE_LIST in self._attrs ): - flags |= SUPPORT_SELECT_SOUND_MODE + flags |= MediaPlayerEntityFeature.SELECT_SOUND_MODE return flags diff --git a/homeassistant/components/upb/light.py b/homeassistant/components/upb/light.py index 1ed1e788936..13f680d9e5a 100644 --- a/homeassistant/components/upb/light.py +++ b/homeassistant/components/upb/light.py @@ -3,10 +3,9 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_FLASH, ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -56,11 +55,23 @@ class UpbLight(UpbAttachedEntity, LightEntity): self._brightness = self._element.status @property - def supported_features(self): + def color_mode(self) -> ColorMode: + """Return the color mode of the light.""" + if self._element.dimmable: + return ColorMode.BRIGHTNESS + return ColorMode.ONOFF + + @property + def supported_color_modes(self) -> set[str]: + """Flag supported color modes.""" + return {self.color_mode} + + @property + def supported_features(self) -> int: """Flag supported features.""" if self._element.dimmable: - return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_FLASH - return SUPPORT_FLASH + return LightEntityFeature.TRANSITION | LightEntityFeature.FLASH + return LightEntityFeature.FLASH @property def brightness(self): diff --git a/homeassistant/components/update/__init__.py b/homeassistant/components/update/__init__.py index 100b5dcc35e..6cc03700ed8 100644 --- a/homeassistant/components/update/__init__.py +++ b/homeassistant/components/update/__init__.py @@ -96,6 +96,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: {}, async_skip, ) + component.async_register_entity_service( + "clear_skipped", + {}, + async_clear_skipped, + ) + websocket_api.async_register_command(hass, websocket_release_notes) return True @@ -153,6 +159,15 @@ async def async_skip(entity: UpdateEntity, service_call: ServiceCall) -> None: await entity.async_skip() +async def async_clear_skipped(entity: UpdateEntity, service_call: ServiceCall) -> None: + """Service call wrapper to validate the call.""" + if entity.auto_update: + raise HomeAssistantError( + f"Clearing skipped update is not supported for {entity.name}" + ) + await entity.async_clear_skipped() + + @dataclass class UpdateEntityDescription(EntityDescription): """A class that describes update entities.""" @@ -276,6 +291,12 @@ class UpdateEntity(RestoreEntity): self.__skipped_version = latest_version self.async_write_ha_state() + @final + async def async_clear_skipped(self) -> None: + """Clear the skipped version.""" + self.__skipped_version = None + self.async_write_ha_state() + async def async_install( self, version: str | None, backup: bool, **kwargs: Any ) -> None: @@ -354,8 +375,11 @@ class UpdateEntity(RestoreEntity): # Clear skipped version in case it matches the current installed # version or the latest version diverged. if ( - self.__skipped_version == self.installed_version - or self.__skipped_version != self.latest_version + self.installed_version is not None + and self.__skipped_version == self.installed_version + ) or ( + self.latest_version is not None + and self.__skipped_version != self.latest_version ): self.__skipped_version = None diff --git a/homeassistant/components/update/device_trigger.py b/homeassistant/components/update/device_trigger.py new file mode 100644 index 00000000000..690e67cce56 --- /dev/null +++ b/homeassistant/components/update/device_trigger.py @@ -0,0 +1,48 @@ +"""Provides device triggers for update entities.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant.components.automation import ( + AutomationActionType, + AutomationTriggerInfo, +) +from homeassistant.components.device_automation import toggle_entity +from homeassistant.const import CONF_DOMAIN +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from . import DOMAIN + +TRIGGER_SCHEMA = vol.All( + toggle_entity.TRIGGER_SCHEMA, + vol.Schema({vol.Required(CONF_DOMAIN): DOMAIN}, extra=vol.ALLOW_EXTRA), +) + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: AutomationTriggerInfo, +) -> CALLBACK_TYPE: + """Listen for state changes based on configuration.""" + return await toggle_entity.async_attach_trigger( + hass, config, action, automation_info + ) + + +async def async_get_triggers( + hass: HomeAssistant, device_id: str +) -> list[dict[str, Any]]: + """List device triggers.""" + return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) + + +async def async_get_trigger_capabilities( + hass: HomeAssistant, config: ConfigType +) -> dict[str, vol.Schema]: + """List trigger capabilities.""" + return await toggle_entity.async_get_trigger_capabilities(hass, config) diff --git a/homeassistant/components/update/services.yaml b/homeassistant/components/update/services.yaml index 2a3370493cc..9b16dbd2713 100644 --- a/homeassistant/components/update/services.yaml +++ b/homeassistant/components/update/services.yaml @@ -25,3 +25,10 @@ skip: target: entity: domain: update + +clear_skipped: + name: Clear skipped update + description: Removes the skipped version marker from an update. + target: + entity: + domain: update diff --git a/homeassistant/components/update/strings.json b/homeassistant/components/update/strings.json index b079c9ec8b6..c26d3968ae1 100644 --- a/homeassistant/components/update/strings.json +++ b/homeassistant/components/update/strings.json @@ -1,3 +1,10 @@ { - "title": "Update" + "title": "Update", + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} update availability changed", + "turned_on": "{entity_name} got an update available", + "turned_off": "{entity_name} became up-to-date" + } + } } diff --git a/homeassistant/components/update/translations/ca.json b/homeassistant/components/update/translations/ca.json index 396e79c14c0..b008c0693c0 100644 --- a/homeassistant/components/update/translations/ca.json +++ b/homeassistant/components/update/translations/ca.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "La disponibilitat de l'actualitzaci\u00f3 de {entity_name} canvi\u00ef", + "turned_off": "{entity_name} s'actualitzi", + "turned_on": "{entity_name} tingui una actualitzaci\u00f3 disponible" + } + }, "title": "Actualitza" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/cs.json b/homeassistant/components/update/translations/cs.json new file mode 100644 index 00000000000..39380573af6 --- /dev/null +++ b/homeassistant/components/update/translations/cs.json @@ -0,0 +1,9 @@ +{ + "device_automation": { + "trigger_type": { + "changed_states": "Dostupnost aktualizace {entity_name} zm\u011bn\u011bna", + "turned_off": "{entity_name} se stal aktu\u00e1ln\u00edm", + "turned_on": "{entity_name} m\u00e1 k dispozici aktualizaci" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/update/translations/de.json b/homeassistant/components/update/translations/de.json index 18562d81eaf..bc3e9b19322 100644 --- a/homeassistant/components/update/translations/de.json +++ b/homeassistant/components/update/translations/de.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} Update Verf\u00fcgbarkeit ge\u00e4ndert", + "turned_off": "{entity_name} wurde auf den neuesten Stand gebracht", + "turned_on": "F\u00fcr {entity_name} ist ein Update verf\u00fcgbar" + } + }, "title": "Aktualisieren" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/el.json b/homeassistant/components/update/translations/el.json index d687d342ec3..0a4d6190150 100644 --- a/homeassistant/components/update/translations/el.json +++ b/homeassistant/components/update/translations/el.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "\u0397 \u03b4\u03b9\u03b1\u03b8\u03b5\u03c3\u03b9\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 {entity_name} \u03ac\u03bb\u03bb\u03b1\u03be\u03b5", + "turned_off": "{entity_name} \u03b5\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", + "turned_on": "{entity_name} \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03b7 \u03bc\u03b9\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" + } + }, "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/en.json b/homeassistant/components/update/translations/en.json index 95b82de3b4d..5b07e7c5245 100644 --- a/homeassistant/components/update/translations/en.json +++ b/homeassistant/components/update/translations/en.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} update availability changed", + "turned_off": "{entity_name} became up-to-date", + "turned_on": "{entity_name} got an update available" + } + }, "title": "Update" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/es.json b/homeassistant/components/update/translations/es.json new file mode 100644 index 00000000000..79993c92b20 --- /dev/null +++ b/homeassistant/components/update/translations/es.json @@ -0,0 +1,3 @@ +{ + "title": "Actualizar" +} \ No newline at end of file diff --git a/homeassistant/components/update/translations/et.json b/homeassistant/components/update/translations/et.json index 7db9a98a507..e89acfbe30f 100644 --- a/homeassistant/components/update/translations/et.json +++ b/homeassistant/components/update/translations/et.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "Olemi {entity_name} v\u00e4rskenduse saadavus muutus", + "turned_off": "Olem {entity_name} muutus ajakohaseks", + "turned_on": "Olemile {entity_name} on saadaval v\u00e4rskendus" + } + }, "title": "Uuenda" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/fr.json b/homeassistant/components/update/translations/fr.json index 1a49a0cab9f..94d23e72a7a 100644 --- a/homeassistant/components/update/translations/fr.json +++ b/homeassistant/components/update/translations/fr.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "La disponibilit\u00e9 d'une mise \u00e0 jour pour {entity_name} a chang\u00e9", + "turned_off": "{entity_name} a \u00e9t\u00e9 mis \u00e0 jour", + "turned_on": "Une mise \u00e0 jour est disponible pour {entity_name}" + } + }, "title": "Mettre \u00e0 jour" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/he.json b/homeassistant/components/update/translations/he.json new file mode 100644 index 00000000000..6c81fac9447 --- /dev/null +++ b/homeassistant/components/update/translations/he.json @@ -0,0 +1,10 @@ +{ + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} \u05d6\u05de\u05d9\u05e0\u05d5\u05ea \u05d4\u05e2\u05d3\u05db\u05d5\u05df \u05d4\u05e9\u05ea\u05e0\u05ea\u05d4", + "turned_off": "{entity_name} \u05d4\u05e4\u05da \u05dc\u05de\u05e2\u05d5\u05d3\u05db\u05df", + "turned_on": "{entity_name} \u05e7\u05d9\u05d1\u05dc \u05e2\u05d3\u05db\u05d5\u05df \u05d6\u05de\u05d9\u05df" + } + }, + "title": "\u05e2\u05d3\u05db\u05d5\u05df" +} \ No newline at end of file diff --git a/homeassistant/components/update/translations/hu.json b/homeassistant/components/update/translations/hu.json index 1e2ec425a88..7732ee56d62 100644 --- a/homeassistant/components/update/translations/hu.json +++ b/homeassistant/components/update/translations/hu.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} friss\u00edt\u00e9s el\u00e9rhet\u0151s\u00e9ge megv\u00e1ltozott", + "turned_off": "{entity_name} naprak\u00e9sz lett", + "turned_on": "{entity_name} kapott egy el\u00e9rhet\u0151 friss\u00edt\u00e9st" + } + }, "title": "Friss\u00edt\u00e9s" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/id.json b/homeassistant/components/update/translations/id.json index 70f495575fa..c4e6c43ab5c 100644 --- a/homeassistant/components/update/translations/id.json +++ b/homeassistant/components/update/translations/id.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "Ketersediaan pembaruan {entity_name} berubah", + "turned_off": "{entity_name} menjadi yang terbaru", + "turned_on": "{entity_name} mendapat pembaruan yang tersedia" + } + }, "title": "Versi Baru" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/it.json b/homeassistant/components/update/translations/it.json index 539f0bb4294..97d2b4020df 100644 --- a/homeassistant/components/update/translations/it.json +++ b/homeassistant/components/update/translations/it.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "La disponibilit\u00e0 dell'aggiornamento di {entity_name} \u00e8 cambiata", + "turned_off": "{entity_name} \u00e8 stato aggiornato", + "turned_on": "{entity_name} ha un aggiornamento disponibile" + } + }, "title": "Aggiornamento" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/ja.json b/homeassistant/components/update/translations/ja.json index 4cb5e4959a1..63ac9a7ddf5 100644 --- a/homeassistant/components/update/translations/ja.json +++ b/homeassistant/components/update/translations/ja.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} \u66f4\u65b0\u306e\u53ef\u7528\u6027(availability)\u304c\u5909\u66f4\u3055\u308c\u307e\u3057\u305f", + "turned_off": "{entity_name} \u304c\u6700\u65b0\u306b\u306a\u308a\u307e\u3057\u305f", + "turned_on": "{entity_name} \u306f\u3001\u5229\u7528\u53ef\u80fd\u306a\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3092\u53d6\u5f97\u3057\u307e\u3057\u305f\u3002" + } + }, "title": "\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/nl.json b/homeassistant/components/update/translations/nl.json index 95b82de3b4d..840fa03da8a 100644 --- a/homeassistant/components/update/translations/nl.json +++ b/homeassistant/components/update/translations/nl.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} update beschikbaarheid gewijzigd", + "turned_off": "{entity_name} werd up-to-date", + "turned_on": "{entity_name} heeft een update beschikbaar" + } + }, "title": "Update" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/no.json b/homeassistant/components/update/translations/no.json index e98d60ab4fc..8f292b0b141 100644 --- a/homeassistant/components/update/translations/no.json +++ b/homeassistant/components/update/translations/no.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} -oppdateringstilgjengeligheten er endret", + "turned_off": "{entity_name} ble oppdatert", + "turned_on": "{entity_name} har en oppdatering tilgjengelig" + } + }, "title": "Oppdater" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/pl.json b/homeassistant/components/update/translations/pl.json index eff0431a518..ee29b58f8c3 100644 --- a/homeassistant/components/update/translations/pl.json +++ b/homeassistant/components/update/translations/pl.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "zmieni si\u0119 dost\u0119pno\u015b\u0107 aktualizacji {entity_name}", + "turned_off": "wykonano aktualizacj\u0119 dla {entity_name}", + "turned_on": "{entity_name} ma dost\u0119pn\u0105 aktualizacj\u0119" + } + }, "title": "Aktualizacja" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/pt-BR.json b/homeassistant/components/update/translations/pt-BR.json index 4003445e2c3..dbdbae4fa88 100644 --- a/homeassistant/components/update/translations/pt-BR.json +++ b/homeassistant/components/update/translations/pt-BR.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} disponibilidade de atualiza\u00e7\u00e3o alterada", + "turned_off": "{entity_name} foi atualizado", + "turned_on": "{entity_name} tem uma atualiza\u00e7\u00e3o dispon\u00edvel" + } + }, "title": "Atualiza\u00e7\u00e3o" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/ru.json b/homeassistant/components/update/translations/ru.json index a2ee79efd15..f78b838708e 100644 --- a/homeassistant/components/update/translations/ru.json +++ b/homeassistant/components/update/translations/ru.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f {entity_name}", + "turned_off": "{entity_name} \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f", + "turned_on": "\u0421\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 {entity_name}" + } + }, "title": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/tr.json b/homeassistant/components/update/translations/tr.json index 30c3c90c437..11d0b3a2c92 100644 --- a/homeassistant/components/update/translations/tr.json +++ b/homeassistant/components/update/translations/tr.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} g\u00fcncellemesinin kullan\u0131labilirli\u011fi de\u011fi\u015fti", + "turned_off": "{entity_name} g\u00fcncellendi", + "turned_on": "{entity_name} bir g\u00fcncelleme ald\u0131" + } + }, "title": "G\u00fcncelle" } \ No newline at end of file diff --git a/homeassistant/components/update/translations/zh-Hans.json b/homeassistant/components/update/translations/zh-Hans.json new file mode 100644 index 00000000000..8b7c885cccb --- /dev/null +++ b/homeassistant/components/update/translations/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} \u7684\u66f4\u65b0\u53ef\u7528\u6027\u53d8\u5316", + "turned_off": "{entity_name} \u53d8\u4e3a\u6700\u65b0\u72b6\u6001", + "turned_on": "{entity_name} \u6536\u5230\u66f4\u65b0" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/update/translations/zh-Hant.json b/homeassistant/components/update/translations/zh-Hant.json index 46cdfb48f90..492af201404 100644 --- a/homeassistant/components/update/translations/zh-Hant.json +++ b/homeassistant/components/update/translations/zh-Hant.json @@ -1,3 +1,10 @@ { + "device_automation": { + "trigger_type": { + "changed_states": "{entity_name} \u53ef\u7528\u66f4\u65b0\u5df2\u8b8a\u66f4", + "turned_off": "{entity_name} \u5df2\u6700\u65b0", + "turned_on": "{entity_name} \u6709\u66f4\u65b0" + } + }, "title": "\u66f4\u65b0" } \ No newline at end of file diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py deleted file mode 100644 index 4f88b5d1369..00000000000 --- a/homeassistant/components/updater/__init__.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Support to check for available updates.""" -import asyncio -from datetime import timedelta -import logging - -import async_timeout -from awesomeversion import AwesomeVersion -import voluptuous as vol - -from homeassistant.components import hassio -from homeassistant.const import Platform, __version__ as current_version -from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery, update_coordinator -from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType - -_LOGGER = logging.getLogger(__name__) - -ATTR_RELEASE_NOTES = "release_notes" -ATTR_NEWEST_VERSION = "newest_version" - -CONF_REPORTING = "reporting" -CONF_COMPONENT_REPORTING = "include_used_components" - -DOMAIN = "updater" - -UPDATER_URL = "https://www.home-assistant.io/version.json" - - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: { - vol.Optional(CONF_REPORTING): cv.boolean, - vol.Optional(CONF_COMPONENT_REPORTING): cv.boolean, - } - }, - extra=vol.ALLOW_EXTRA, -) - -RESPONSE_SCHEMA = vol.Schema( - {vol.Required("current_version"): cv.string, vol.Required("release_notes"): cv.url}, - extra=vol.REMOVE_EXTRA, -) - - -class Updater: - """Updater class for data exchange.""" - - def __init__( - self, update_available: bool, newest_version: str, release_notes: str - ) -> None: - """Initialize attributes.""" - self.update_available = update_available - self.release_notes = release_notes - self.newest_version = newest_version - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the updater component.""" - _LOGGER.warning( - "The updater integration has been deprecated and will be removed in 2022.5, " - "please remove it from your configuration" - ) - - async def check_new_version() -> Updater: - """Check if a new version is available and report if one is.""" - # Skip on dev - if "dev" in current_version: - return Updater(False, "", "") - - newest, release_notes = await get_newest_version(hass) - - _LOGGER.debug("Fetched version %s: %s", newest, release_notes) - - # Load data from Supervisor - if hassio.is_hassio(hass): - core_info = hassio.get_core_info(hass) - newest = core_info["version_latest"] - - # Validate version - update_available = False - if AwesomeVersion(newest) > AwesomeVersion(current_version): - _LOGGER.debug( - "The latest available version of Home Assistant is %s", newest - ) - update_available = True - elif AwesomeVersion(newest) == AwesomeVersion(current_version): - _LOGGER.debug( - "You are on the latest version (%s) of Home Assistant", newest - ) - elif AwesomeVersion(newest) < AwesomeVersion(current_version): - _LOGGER.debug( - "Local version (%s) is newer than the latest available version (%s)", - current_version, - newest, - ) - - _LOGGER.debug("Update available: %s", update_available) - - return Updater(update_available, newest, release_notes) - - coordinator = hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator[Updater]( - hass, - _LOGGER, - name="Home Assistant update", - update_method=check_new_version, - update_interval=timedelta(days=1), - ) - - # This can take up to 15s which can delay startup - asyncio.create_task(coordinator.async_refresh()) - - hass.async_create_task( - discovery.async_load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) - ) - - return True - - -async def get_newest_version(hass): - """Get the newest Home Assistant version.""" - session = async_get_clientsession(hass) - - async with async_timeout.timeout(30): - req = await session.get(UPDATER_URL) - - try: - res = await req.json() - except ValueError as err: - raise update_coordinator.UpdateFailed( - "Received invalid JSON from Home Assistant Update" - ) from err - - try: - res = RESPONSE_SCHEMA(res) - return res["current_version"], res["release_notes"] - except vol.Invalid as err: - raise update_coordinator.UpdateFailed( - f"Got unexpected response: {err}" - ) from err diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py deleted file mode 100644 index 1409f26a2f5..00000000000 --- a/homeassistant/components/updater/binary_sensor.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Support for Home Assistant Updater binary sensors.""" -from __future__ import annotations - -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntity, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from . import ATTR_NEWEST_VERSION, ATTR_RELEASE_NOTES, DOMAIN as UPDATER_DOMAIN - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the updater binary sensors.""" - if discovery_info is None: - return - - async_add_entities([UpdaterBinary(hass.data[UPDATER_DOMAIN])]) - - -class UpdaterBinary(CoordinatorEntity, BinarySensorEntity): - """Representation of an updater binary sensor.""" - - _attr_device_class = BinarySensorDeviceClass.UPDATE - _attr_name = "Updater" - _attr_unique_id = "updater" - - @property - def available(self) -> bool: - """Return if entity is available.""" - return True - - @property - def is_on(self) -> bool: - """Return true if there is an update available.""" - return self.coordinator.data and self.coordinator.data.update_available - - @property - def extra_state_attributes(self) -> dict | None: - """Return the optional state attributes.""" - if not self.coordinator.data: - return None - data = {} - if self.coordinator.data.release_notes: - data[ATTR_RELEASE_NOTES] = self.coordinator.data.release_notes - if self.coordinator.data.newest_version: - data[ATTR_NEWEST_VERSION] = self.coordinator.data.newest_version - return data diff --git a/homeassistant/components/updater/manifest.json b/homeassistant/components/updater/manifest.json deleted file mode 100644 index db225bbf242..00000000000 --- a/homeassistant/components/updater/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "updater", - "name": "Updater", - "documentation": "https://www.home-assistant.io/integrations/updater", - "codeowners": ["@home-assistant/core"], - "quality_scale": "internal", - "iot_class": "cloud_polling" -} diff --git a/homeassistant/components/updater/strings.json b/homeassistant/components/updater/strings.json deleted file mode 100644 index d4fe2079d8f..00000000000 --- a/homeassistant/components/updater/strings.json +++ /dev/null @@ -1 +0,0 @@ -{ "title": "Updater" } diff --git a/homeassistant/components/updater/translations/af.json b/homeassistant/components/updater/translations/af.json deleted file mode 100644 index bf9cb9c98f4..00000000000 --- a/homeassistant/components/updater/translations/af.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Opdateerder" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ar.json b/homeassistant/components/updater/translations/ar.json deleted file mode 100644 index 9aecb4b83dc..00000000000 --- a/homeassistant/components/updater/translations/ar.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u062a\u062d\u062f\u064a\u062b" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/bg.json b/homeassistant/components/updater/translations/bg.json deleted file mode 100644 index ce1bddc104f..00000000000 --- a/homeassistant/components/updater/translations/bg.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u041e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/bs.json b/homeassistant/components/updater/translations/bs.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/bs.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ca.json b/homeassistant/components/updater/translations/ca.json deleted file mode 100644 index 419215d32b6..00000000000 --- a/homeassistant/components/updater/translations/ca.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Actualitzador" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/cs.json b/homeassistant/components/updater/translations/cs.json deleted file mode 100644 index 9d25158400b..00000000000 --- a/homeassistant/components/updater/translations/cs.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Aktualiz\u00e1tor" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/cy.json b/homeassistant/components/updater/translations/cy.json deleted file mode 100644 index b3ef0dcb85f..00000000000 --- a/homeassistant/components/updater/translations/cy.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Diweddarwr" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/da.json b/homeassistant/components/updater/translations/da.json deleted file mode 100644 index bc9b108c3ec..00000000000 --- a/homeassistant/components/updater/translations/da.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Opdater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/de.json b/homeassistant/components/updater/translations/de.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/de.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/el.json b/homeassistant/components/updater/translations/el.json deleted file mode 100644 index f44dc928c16..00000000000 --- a/homeassistant/components/updater/translations/el.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03c9\u03c4\u03ae\u03c2" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/en.json b/homeassistant/components/updater/translations/en.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/es-419.json b/homeassistant/components/updater/translations/es-419.json deleted file mode 100644 index a822ffbd0a9..00000000000 --- a/homeassistant/components/updater/translations/es-419.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Actualizador" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/es.json b/homeassistant/components/updater/translations/es.json deleted file mode 100644 index a822ffbd0a9..00000000000 --- a/homeassistant/components/updater/translations/es.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Actualizador" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/et.json b/homeassistant/components/updater/translations/et.json deleted file mode 100644 index 8d36316f011..00000000000 --- a/homeassistant/components/updater/translations/et.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Uuendaja" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/eu.json b/homeassistant/components/updater/translations/eu.json deleted file mode 100644 index cec08736bae..00000000000 --- a/homeassistant/components/updater/translations/eu.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Eguneratzailea" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/fa.json b/homeassistant/components/updater/translations/fa.json deleted file mode 100644 index d32b1e212c2..00000000000 --- a/homeassistant/components/updater/translations/fa.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u0628\u0647 \u0631\u0648\u0632 \u0631\u0633\u0627\u0646" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/fi.json b/homeassistant/components/updater/translations/fi.json deleted file mode 100644 index 48f9aa81b72..00000000000 --- a/homeassistant/components/updater/translations/fi.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "P\u00e4ivitys" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/fr.json b/homeassistant/components/updater/translations/fr.json deleted file mode 100644 index 228912f95a8..00000000000 --- a/homeassistant/components/updater/translations/fr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Mise \u00e0 jour" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/gsw.json b/homeassistant/components/updater/translations/gsw.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/gsw.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/he.json b/homeassistant/components/updater/translations/he.json deleted file mode 100644 index 38072833421..00000000000 --- a/homeassistant/components/updater/translations/he.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u05de\u05e2\u05d3\u05db\u05df" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/hr.json b/homeassistant/components/updater/translations/hr.json deleted file mode 100644 index 21d0438f9cb..00000000000 --- a/homeassistant/components/updater/translations/hr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "A\u017euriranje" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/hu.json b/homeassistant/components/updater/translations/hu.json deleted file mode 100644 index e862dcb360c..00000000000 --- a/homeassistant/components/updater/translations/hu.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Friss\u00edt\u0151" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/hy.json b/homeassistant/components/updater/translations/hy.json deleted file mode 100644 index 78c67fb8950..00000000000 --- a/homeassistant/components/updater/translations/hy.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u0539\u0561\u0580\u0574\u0561\u0581\u0576\u0578\u0572" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/id.json b/homeassistant/components/updater/translations/id.json deleted file mode 100644 index 1ab6aa58946..00000000000 --- a/homeassistant/components/updater/translations/id.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Pembaru" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/is.json b/homeassistant/components/updater/translations/is.json deleted file mode 100644 index e0f7536fd1a..00000000000 --- a/homeassistant/components/updater/translations/is.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Uppf\u00e6rslu\u00e1lfur" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/it.json b/homeassistant/components/updater/translations/it.json deleted file mode 100644 index 539f0bb4294..00000000000 --- a/homeassistant/components/updater/translations/it.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Aggiornamento" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ja.json b/homeassistant/components/updater/translations/ja.json deleted file mode 100644 index 2a34917b909..00000000000 --- a/homeassistant/components/updater/translations/ja.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u30a2\u30c3\u30d7\u30c7\u30fc\u30bf\u30fc" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ko.json b/homeassistant/components/updater/translations/ko.json deleted file mode 100644 index 14137569e1b..00000000000 --- a/homeassistant/components/updater/translations/ko.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\uc5c5\ub370\uc774\ud130" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/lb.json b/homeassistant/components/updater/translations/lb.json deleted file mode 100644 index 375f8fa7bc6..00000000000 --- a/homeassistant/components/updater/translations/lb.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Aktualis\u00e9ierung" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/lv.json b/homeassistant/components/updater/translations/lv.json deleted file mode 100644 index 15d29e35a06..00000000000 --- a/homeassistant/components/updater/translations/lv.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Atjaunin\u0101t\u0101js" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/nb.json b/homeassistant/components/updater/translations/nb.json deleted file mode 100644 index e98d60ab4fc..00000000000 --- a/homeassistant/components/updater/translations/nb.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Oppdater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/nl.json b/homeassistant/components/updater/translations/nl.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/nl.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/nn.json b/homeassistant/components/updater/translations/nn.json deleted file mode 100644 index 7eb98bdd2c1..00000000000 --- a/homeassistant/components/updater/translations/nn.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Oppdateringar" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/no.json b/homeassistant/components/updater/translations/no.json deleted file mode 100644 index c8fafabfe77..00000000000 --- a/homeassistant/components/updater/translations/no.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Oppdaterer" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/pl.json b/homeassistant/components/updater/translations/pl.json deleted file mode 100644 index 21a3703bba9..00000000000 --- a/homeassistant/components/updater/translations/pl.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Aktualizator" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/pt-BR.json b/homeassistant/components/updater/translations/pt-BR.json deleted file mode 100644 index cc89a22092a..00000000000 --- a/homeassistant/components/updater/translations/pt-BR.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Gerenciador de atualiza\u00e7\u00f5es" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/pt.json b/homeassistant/components/updater/translations/pt.json deleted file mode 100644 index 7d07ec8da09..00000000000 --- a/homeassistant/components/updater/translations/pt.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Atualizador" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ro.json b/homeassistant/components/updater/translations/ro.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/ro.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ru.json b/homeassistant/components/updater/translations/ru.json deleted file mode 100644 index a2ee79efd15..00000000000 --- a/homeassistant/components/updater/translations/ru.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/sk.json b/homeassistant/components/updater/translations/sk.json deleted file mode 100644 index 9d25158400b..00000000000 --- a/homeassistant/components/updater/translations/sk.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Aktualiz\u00e1tor" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/sl.json b/homeassistant/components/updater/translations/sl.json deleted file mode 100644 index 7972844cb69..00000000000 --- a/homeassistant/components/updater/translations/sl.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Posodabljalnik" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/sv.json b/homeassistant/components/updater/translations/sv.json deleted file mode 100644 index 78ef7d2df20..00000000000 --- a/homeassistant/components/updater/translations/sv.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Uppdaterare" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/ta.json b/homeassistant/components/updater/translations/ta.json deleted file mode 100644 index 74f9398fbcb..00000000000 --- a/homeassistant/components/updater/translations/ta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u0b85\u0baa\u0bcd\u0b9f\u0bc7\u0b9f\u0bcd\u0b9f\u0bb0\u0bcd" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/te.json b/homeassistant/components/updater/translations/te.json deleted file mode 100644 index 43859eedc5a..00000000000 --- a/homeassistant/components/updater/translations/te.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Updater" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/th.json b/homeassistant/components/updater/translations/th.json deleted file mode 100644 index d825a885d68..00000000000 --- a/homeassistant/components/updater/translations/th.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u0e2d\u0e31\u0e1e\u0e40\u0e14\u0e15" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/tr.json b/homeassistant/components/updater/translations/tr.json deleted file mode 100644 index 7034ef0d79e..00000000000 --- a/homeassistant/components/updater/translations/tr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "G\u00fcncelleyici" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/uk.json b/homeassistant/components/updater/translations/uk.json deleted file mode 100644 index e98d67fc206..00000000000 --- a/homeassistant/components/updater/translations/uk.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u041e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/vi.json b/homeassistant/components/updater/translations/vi.json deleted file mode 100644 index 0e2783d6f21..00000000000 --- a/homeassistant/components/updater/translations/vi.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Tr\u00ecnh c\u1eadp nh\u1eadt" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/zh-Hans.json b/homeassistant/components/updater/translations/zh-Hans.json deleted file mode 100644 index 154ab2b812b..00000000000 --- a/homeassistant/components/updater/translations/zh-Hans.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u66f4\u65b0\u63d0\u793a" -} \ No newline at end of file diff --git a/homeassistant/components/updater/translations/zh-Hant.json b/homeassistant/components/updater/translations/zh-Hant.json deleted file mode 100644 index 23c1b069fc1..00000000000 --- a/homeassistant/components/updater/translations/zh-Hant.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "\u66f4\u65b0\u5668" -} \ No newline at end of file diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index e15c90c7079..b571a2b447f 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -8,7 +8,7 @@ from datetime import timedelta from ipaddress import ip_address from typing import Any -from async_upnp_client.exceptions import UpnpConnectionError +from async_upnp_client.exceptions import UpnpCommunicationError, UpnpConnectionError import voluptuous as vol from homeassistant import config_entries @@ -26,20 +26,20 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, + UpdateFailed, ) from .const import ( CONF_LOCAL_IP, - CONFIG_ENTRY_HOSTNAME, - CONFIG_ENTRY_SCAN_INTERVAL, + CONFIG_ENTRY_MAC_ADDRESS, + CONFIG_ENTRY_ORIGINAL_UDN, CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, DEFAULT_SCAN_INTERVAL, DOMAIN, - DOMAIN_DEVICES, LOGGER, ) -from .device import Device +from .device import Device, async_get_mac_address_from_host NOTIFICATION_ID = "upnp_notification" NOTIFICATION_TITLE = "UPnP/IGD Setup" @@ -66,9 +66,7 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up UPnP component.""" - hass.data[DOMAIN] = { - DOMAIN_DEVICES: {}, - } + hass.data[DOMAIN] = {} # Only start if set up via configuration.yaml. if DOMAIN in config: @@ -83,7 +81,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up UPnP/IGD device from a config entry.""" - LOGGER.debug("Setting up config entry: %s", entry.unique_id) + LOGGER.debug("Setting up config entry: %s", entry.entry_id) udn = entry.data[CONFIG_ENTRY_UDN] st = entry.data[CONFIG_ENTRY_ST] # pylint: disable=invalid-name @@ -127,70 +125,99 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: device = await Device.async_create_device(hass, location) except UpnpConnectionError as err: - LOGGER.debug("Error connecting to device %s", location) + LOGGER.debug( + "Error connecting to device at location: %s, err: %s", location, err + ) raise ConfigEntryNotReady from err - # Ensure entry has a unique_id. - if not entry.unique_id: - LOGGER.debug( - "Setting unique_id: %s, for config_entry: %s", - device.unique_id, - entry, - ) + # Track the original UDN such that existing sensors do not change their unique_id. + if CONFIG_ENTRY_ORIGINAL_UDN not in entry.data: hass.config_entries.async_update_entry( entry=entry, - unique_id=device.unique_id, + data={ + **entry.data, + CONFIG_ENTRY_ORIGINAL_UDN: device.udn, + }, ) + device.original_udn = entry.data[CONFIG_ENTRY_ORIGINAL_UDN] - # Ensure entry has a hostname, for older entries. - if ( - CONFIG_ENTRY_HOSTNAME not in entry.data - or entry.data[CONFIG_ENTRY_HOSTNAME] != device.hostname - ): + # Store mac address for changed UDN matching. + if device.host: + device.mac_address = await async_get_mac_address_from_host(hass, device.host) + if device.mac_address and not entry.data.get("CONFIG_ENTRY_MAC_ADDRESS"): hass.config_entries.async_update_entry( entry=entry, - data={CONFIG_ENTRY_HOSTNAME: device.hostname, **entry.data}, + data={ + **entry.data, + CONFIG_ENTRY_MAC_ADDRESS: device.mac_address, + }, ) - # Create device registry entry. + connections = {(dr.CONNECTION_UPNP, device.udn)} + if device.mac_address: + connections.add((dr.CONNECTION_NETWORK_MAC, device.mac_address)) + device_registry = dr.async_get(hass) - device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - connections={(dr.CONNECTION_UPNP, device.udn)}, - identifiers={(DOMAIN, device.udn)}, - name=device.name, - manufacturer=device.manufacturer, - model=device.model_name, + device_entry = device_registry.async_get_device( + identifiers=set(), connections=connections ) + if device_entry: + LOGGER.debug( + "Found device using connections: %s, device_entry: %s", + connections, + device_entry, + ) + if not device_entry: + # No device found, create new device entry. + device_entry = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections=connections, + identifiers={(DOMAIN, device.usn)}, + name=device.name, + manufacturer=device.manufacturer, + model=device.model_name, + ) + LOGGER.debug( + "Created device using UDN '%s', device_entry: %s", device.udn, device_entry + ) + else: + # Update identifier. + device_entry = device_registry.async_update_device( + device_entry.id, + new_identifiers={(DOMAIN, device.usn)}, + ) - update_interval_sec = entry.options.get( - CONFIG_ENTRY_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ) - update_interval = timedelta(seconds=update_interval_sec) - LOGGER.debug("update_interval: %s", update_interval) + assert device_entry + update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL) coordinator = UpnpDataUpdateCoordinator( hass, device=device, + device_entry=device_entry, update_interval=update_interval, ) + # Try an initial refresh. + await coordinator.async_config_entry_first_refresh() + # Save coordinator. hass.data[DOMAIN][entry.entry_id] = coordinator - await coordinator.async_config_entry_first_refresh() - # Setup platforms, creating sensors/binary_sensors. hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True -async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a UPnP/IGD device from a config entry.""" - LOGGER.debug("Unloading config entry: %s", config_entry.unique_id) + LOGGER.debug("Unloading config entry: %s", entry.entry_id) # Unload platforms. - return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + del hass.data[DOMAIN][entry.entry_id] + + return unload_ok @dataclass @@ -213,26 +240,45 @@ class UpnpDataUpdateCoordinator(DataUpdateCoordinator): """Define an object to update data from UPNP device.""" def __init__( - self, hass: HomeAssistant, device: Device, update_interval: timedelta + self, + hass: HomeAssistant, + device: Device, + device_entry: dr.DeviceEntry, + update_interval: timedelta, ) -> None: """Initialize.""" self.device = device + self.device_entry = device_entry super().__init__( - hass, LOGGER, name=device.name, update_interval=update_interval + hass, + LOGGER, + name=device.name, + update_interval=update_interval, + update_method=self._async_fetch_data, ) - async def _async_update_data(self) -> Mapping[str, Any]: + async def _async_fetch_data(self) -> Mapping[str, Any]: """Update data.""" - update_values = await asyncio.gather( - self.device.async_get_traffic_data(), - self.device.async_get_status(), - ) + try: + update_values = await asyncio.gather( + self.device.async_get_traffic_data(), + self.device.async_get_status(), + ) - return { - **update_values[0], - **update_values[1], - } + return { + **update_values[0], + **update_values[1], + } + except UpnpCommunicationError as exception: + LOGGER.debug( + "Caught exception when updating device: %s, exception: %s", + self.device, + exception, + ) + raise UpdateFailed( + f"Unable to communicate with IGD at: {self.device.device_url}" + ) from exception class UpnpEntity(CoordinatorEntity[UpnpDataUpdateCoordinator]): @@ -251,13 +297,13 @@ class UpnpEntity(CoordinatorEntity[UpnpDataUpdateCoordinator]): self._device = coordinator.device self.entity_description = entity_description self._attr_name = f"{coordinator.device.name} {entity_description.name}" - self._attr_unique_id = f"{coordinator.device.udn}_{entity_description.unique_id or entity_description.key}" + self._attr_unique_id = f"{coordinator.device.original_udn}_{entity_description.unique_id or entity_description.key}" self._attr_device_info = DeviceInfo( - connections={(dr.CONNECTION_UPNP, coordinator.device.udn)}, - name=coordinator.device.name, - manufacturer=coordinator.device.manufacturer, - model=coordinator.device.model_name, - configuration_url=f"http://{coordinator.device.hostname}", + connections=coordinator.device_entry.connections, + name=coordinator.device_entry.name, + manufacturer=coordinator.device_entry.manufacturer, + model=coordinator.device_entry.model, + configuration_url=coordinator.device_entry.configuration_url, ) @property diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index e339c69d5d8..b7208e6e6e2 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio from collections.abc import Mapping -from datetime import timedelta from typing import Any, cast import voluptuous as vol @@ -11,22 +10,22 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.ssdp import SsdpChange, SsdpServiceInfo -from homeassistant.const import CONF_SCAN_INTERVAL -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult from .const import ( - CONFIG_ENTRY_HOSTNAME, - CONFIG_ENTRY_SCAN_INTERVAL, + CONFIG_ENTRY_LOCATION, + CONFIG_ENTRY_MAC_ADDRESS, + CONFIG_ENTRY_ORIGINAL_UDN, CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, - DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, SSDP_SEARCH_TIMEOUT, ST_IGD_V1, ST_IGD_V2, ) +from .device import async_get_mac_address_from_host def _friendly_name_from_discovery(discovery_info: ssdp.SsdpServiceInfo) -> str: @@ -54,15 +53,13 @@ async def _async_wait_for_discoveries(hass: HomeAssistant) -> bool: device_discovered_event = asyncio.Event() async def device_discovered(info: SsdpServiceInfo, change: SsdpChange) -> None: - if change == SsdpChange.BYEBYE: - return - - LOGGER.info( - "Device discovered: %s, at: %s", - info.ssdp_usn, - info.ssdp_location, - ) - device_discovered_event.set() + if change != SsdpChange.BYEBYE: + LOGGER.info( + "Device discovered: %s, at: %s", + info.ssdp_usn, + info.ssdp_location, + ) + device_discovered_event.set() cancel_discovered_callback_1 = await ssdp.async_register_callback( hass, @@ -101,6 +98,14 @@ async def _async_discover_igd_devices( ) + await ssdp.async_get_discovery_info_by_st(hass, ST_IGD_V2) +async def _async_mac_address_from_discovery( + hass: HomeAssistant, discovery: SsdpServiceInfo +) -> str | None: + """Get the mac address from a discovery.""" + host = discovery.ssdp_headers["_host"] + return await async_get_mac_address_from_host(hass, host) + + class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a UPnP/IGD config flow.""" @@ -122,15 +127,13 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: # Ensure wanted device was discovered. assert self._discoveries - matching_discoveries = [ - discovery - for discovery in self._discoveries - if discovery.ssdp_usn == user_input["unique_id"] - ] - if not matching_discoveries: - return self.async_abort(reason="no_devices_found") - - discovery = matching_discoveries[0] + discovery = next( + iter( + discovery + for discovery in self._discoveries + if discovery.ssdp_usn == user_input["unique_id"] + ) + ) await self.async_set_unique_id(discovery.ssdp_usn, raise_on_progress=False) return await self._async_create_entry_from_discovery(discovery) @@ -221,21 +224,46 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Ensure not already configuring/configured. unique_id = discovery_info.ssdp_usn await self.async_set_unique_id(unique_id) - hostname = discovery_info.ssdp_headers["_host"] + mac_address = await _async_mac_address_from_discovery(self.hass, discovery_info) self._abort_if_unique_id_configured( - updates={CONFIG_ENTRY_HOSTNAME: hostname}, reload_on_update=False + # Store mac address for older entries. + # The location is stored in the config entry such that when the location changes, the entry is reloaded. + updates={ + CONFIG_ENTRY_MAC_ADDRESS: mac_address, + CONFIG_ENTRY_LOCATION: discovery_info.ssdp_location, + }, ) # Handle devices changing their UDN, only allow a single host. - existing_entries = self._async_current_entries() - for config_entry in existing_entries: - entry_hostname = config_entry.data.get(CONFIG_ENTRY_HOSTNAME) - if entry_hostname == hostname: - LOGGER.debug( - "Found existing config_entry with same hostname, discovery ignored" - ) + for entry in self._async_current_entries(include_ignore=True): + entry_mac_address = entry.data.get(CONFIG_ENTRY_MAC_ADDRESS) + entry_st = entry.data.get(CONFIG_ENTRY_ST) + if entry_mac_address != mac_address: + continue + + if discovery_info.ssdp_st != entry_st: + # Check ssdp_st to prevent swapping between IGDv1 and IGDv2. + continue + + if entry.source == config_entries.SOURCE_IGNORE: + # Host was already ignored. Don't update ignored entries. return self.async_abort(reason="discovery_ignored") + LOGGER.debug("Updating entry: %s", entry.entry_id) + self.hass.config_entries.async_update_entry( + entry, + unique_id=unique_id, + data={**entry.data, CONFIG_ENTRY_UDN: discovery_info.ssdp_udn}, + ) + if entry.state == config_entries.ConfigEntryState.LOADED: + # Only reload when entry has state LOADED; when entry has state SETUP_RETRY, + # another load is started, causing the entry to be loaded twice. + LOGGER.debug("Reloading entry: %s", entry.entry_id) + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + return self.async_abort(reason="config_entry_updated") + # Store discovery. self._discoveries = [discovery_info] @@ -258,14 +286,6 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): discovery = self._discoveries[0] return await self._async_create_entry_from_discovery(discovery) - @staticmethod - @callback - def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: - """Define the config flow to handle options.""" - return UpnpOptionsFlowHandler(config_entry) - async def _async_create_entry_from_discovery( self, discovery: SsdpServiceInfo, @@ -277,44 +297,12 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) title = _friendly_name_from_discovery(discovery) + mac_address = await _async_mac_address_from_discovery(self.hass, discovery) data = { CONFIG_ENTRY_UDN: discovery.upnp[ssdp.ATTR_UPNP_UDN], CONFIG_ENTRY_ST: discovery.ssdp_st, - CONFIG_ENTRY_HOSTNAME: discovery.ssdp_headers["_host"], + CONFIG_ENTRY_ORIGINAL_UDN: discovery.upnp[ssdp.ATTR_UPNP_UDN], + CONFIG_ENTRY_LOCATION: discovery.ssdp_location, + CONFIG_ENTRY_MAC_ADDRESS: mac_address, } return self.async_create_entry(title=title, data=data) - - -class UpnpOptionsFlowHandler(config_entries.OptionsFlow): - """Handle a UPnP options flow.""" - - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: - """Initialize.""" - self.config_entry = config_entry - - async def async_step_init(self, user_input: Mapping = None) -> FlowResult: - """Manage the options.""" - if user_input is not None: - coordinator = self.hass.data[DOMAIN][self.config_entry.entry_id] - update_interval_sec = user_input.get( - CONFIG_ENTRY_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ) - update_interval = timedelta(seconds=update_interval_sec) - LOGGER.debug("Updating coordinator, update_interval: %s", update_interval) - coordinator.update_interval = update_interval - return self.async_create_entry(title="", data=user_input) - - scan_interval = self.config_entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ) - return self.async_show_form( - step_id="init", - data_schema=vol.Schema( - { - vol.Optional( - CONF_SCAN_INTERVAL, - default=scan_interval, - ): vol.All(vol.Coerce(int), vol.Range(min=30)), - } - ), - ) diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py index 0cfd21b74f2..e673922d1c2 100644 --- a/homeassistant/components/upnp/const.py +++ b/homeassistant/components/upnp/const.py @@ -8,7 +8,6 @@ LOGGER = logging.getLogger(__package__) CONF_LOCAL_IP = "local_ip" DOMAIN = "upnp" -DOMAIN_DEVICES = "devices" BYTES_RECEIVED = "bytes_received" BYTES_SENT = "bytes_sent" PACKETS_RECEIVED = "packets_received" @@ -20,10 +19,11 @@ WAN_STATUS = "wan_status" ROUTER_IP = "ip" ROUTER_UPTIME = "uptime" KIBIBYTE = 1024 -CONFIG_ENTRY_SCAN_INTERVAL = "scan_interval" CONFIG_ENTRY_ST = "st" CONFIG_ENTRY_UDN = "udn" -CONFIG_ENTRY_HOSTNAME = "hostname" +CONFIG_ENTRY_ORIGINAL_UDN = "original_udn" +CONFIG_ENTRY_MAC_ADDRESS = "mac_address" +CONFIG_ENTRY_LOCATION = "location" DEFAULT_SCAN_INTERVAL = timedelta(seconds=30).total_seconds() ST_IGD_V1 = "urn:schemas-upnp-org:device:InternetGatewayDevice:1" ST_IGD_V2 = "urn:schemas-upnp-org:device:InternetGatewayDevice:2" diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index a3b5e63bf41..0e7c7902bd9 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -3,6 +3,8 @@ from __future__ import annotations import asyncio from collections.abc import Mapping +from functools import partial +from ipaddress import ip_address from typing import Any from urllib.parse import urlparse @@ -11,9 +13,8 @@ from async_upnp_client.client import UpnpDevice from async_upnp_client.client_factory import UpnpFactory from async_upnp_client.exceptions import UpnpError from async_upnp_client.profiles.igd import IgdDevice, StatusInfo +from getmac import get_mac_address -from homeassistant.components import ssdp -from homeassistant.components.ssdp import SsdpChange, SsdpServiceInfo from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -32,6 +33,20 @@ from .const import ( ) +async def async_get_mac_address_from_host(hass: HomeAssistant, host: str) -> str | None: + """Get mac address from host.""" + ip_addr = ip_address(host) + if ip_addr.version == 4: + mac_address = await hass.async_add_executor_job( + partial(get_mac_address, ip=host) + ) + else: + mac_address = await hass.async_add_executor_job( + partial(get_mac_address, ip6=host) + ) + return mac_address + + async def async_create_upnp_device( hass: HomeAssistant, ssdp_location: str ) -> UpnpDevice: @@ -51,6 +66,7 @@ class Device: self.hass = hass self._igd_device = igd_device self.coordinator: DataUpdateCoordinator | None = None + self._mac_address: str | None = None @classmethod async def async_create_device( @@ -63,36 +79,27 @@ class Device: igd_device = IgdDevice(upnp_device, None) device = cls(hass, igd_device) - # Register SSDP callback for updates. - usn = f"{upnp_device.udn}::{upnp_device.device_type}" - await ssdp.async_register_callback( - hass, device.async_ssdp_callback, {"usn": usn} - ) - return device - async def async_ssdp_callback( - self, service_info: SsdpServiceInfo, change: SsdpChange - ) -> None: - """SSDP callback, update if needed.""" - _LOGGER.debug( - "SSDP Callback, change: %s, headers: %s", change, service_info.ssdp_headers - ) - if service_info.ssdp_location is None: - return + @property + def mac_address(self) -> str | None: + """Get the mac address.""" + return self._mac_address - if change == SsdpChange.ALIVE: - # We care only about updates. - return + @mac_address.setter + def mac_address(self, mac_address: str) -> None: + """Set the mac address.""" + self._mac_address = mac_address - device = self._igd_device.device - if service_info.ssdp_location == device.device_url: - return + @property + def original_udn(self) -> str | None: + """Get the mac address.""" + return self._original_udn - new_upnp_device = await async_create_upnp_device( - self.hass, service_info.ssdp_location - ) - device.reinit(new_upnp_device) + @original_udn.setter + def original_udn(self, original_udn: str) -> None: + """Set the original UDN.""" + self._original_udn = original_udn @property def udn(self) -> str: @@ -130,12 +137,22 @@ class Device: return self.usn @property - def hostname(self) -> str | None: + def host(self) -> str | None: """Get the hostname.""" url = self._igd_device.device.device_url parsed = urlparse(url) return parsed.hostname + @property + def device_url(self) -> str: + """Get the device_url of the device.""" + return self._igd_device.device.device_url + + @property + def serial_number(self) -> str | None: + """Get the serial number.""" + return self._igd_device.device.serial_number + def __str__(self) -> str: """Get string representation.""" return f"IGD Device: {self.name}/{self.udn}::{self.device_type}" @@ -179,7 +196,7 @@ class Device: return_exceptions=True, ) status_info: StatusInfo | None = None - ip_address: str | None = None + router_ip: str | None = None for idx, value in enumerate(values): if isinstance(value, UpnpError): @@ -199,10 +216,10 @@ class Device: if isinstance(value, StatusInfo): status_info = value elif isinstance(value, str): - ip_address = value + router_ip = value return { WAN_STATUS: status_info[0] if status_info is not None else None, ROUTER_UPTIME: status_info[2] if status_info is not None else None, - ROUTER_IP: ip_address, + ROUTER_IP: router_ip, } diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index f3c11c61841..94d9c8ab058 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -3,7 +3,7 @@ "name": "UPnP/IGD", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upnp", - "requirements": ["async-upnp-client==0.27.0"], + "requirements": ["async-upnp-client==0.29.0", "getmac==0.8.2"], "dependencies": ["network", "ssdp"], "codeowners": ["@StevenLooman", "@ehendrix23"], "ssdp": [ diff --git a/homeassistant/components/upnp/translations/it.json b/homeassistant/components/upnp/translations/it.json index ce637bbad08..a57429ac78a 100644 --- a/homeassistant/components/upnp/translations/it.json +++ b/homeassistant/components/upnp/translations/it.json @@ -6,14 +6,14 @@ "no_devices_found": "Nessun dispositivo trovato sulla rete" }, "error": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "flow_title": "{name}", "step": { "init": { - "one": "Pi\u00f9", - "other": "Altri" + "one": "Vuoto", + "other": "Vuoti" }, "ssdp_confirm": { "description": "Vuoi configurare questo dispositivo UPnP/IGD?" diff --git a/homeassistant/components/uptime/translations/bg.json b/homeassistant/components/uptime/translations/bg.json new file mode 100644 index 00000000000..1290144ec04 --- /dev/null +++ b/homeassistant/components/uptime/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435\u0442\u043e?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptime/translations/cs.json b/homeassistant/components/uptime/translations/cs.json new file mode 100644 index 00000000000..ce19e127348 --- /dev/null +++ b/homeassistant/components/uptime/translations/cs.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + }, + "step": { + "user": { + "description": "Chcete za\u010d\u00edt nastavovat?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptime/translations/id.json b/homeassistant/components/uptime/translations/id.json new file mode 100644 index 00000000000..bf6ea606f2b --- /dev/null +++ b/homeassistant/components/uptime/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?" + } + } + }, + "title": "Uptime" +} \ No newline at end of file diff --git a/homeassistant/components/uptime/translations/it.json b/homeassistant/components/uptime/translations/it.json index cba8d0a264f..9913180a309 100644 --- a/homeassistant/components/uptime/translations/it.json +++ b/homeassistant/components/uptime/translations/it.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, "step": { "user": { "description": "Vuoi iniziare la configurazione?" diff --git a/homeassistant/components/uptime/translations/no.json b/homeassistant/components/uptime/translations/no.json new file mode 100644 index 00000000000..9ac16f0a20c --- /dev/null +++ b/homeassistant/components/uptime/translations/no.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "step": { + "user": { + "description": "Vil du starte oppsettet?" + } + } + }, + "title": "Oppetid" +} \ No newline at end of file diff --git a/homeassistant/components/uptime/translations/pl.json b/homeassistant/components/uptime/translations/pl.json new file mode 100644 index 00000000000..bca14b2f14c --- /dev/null +++ b/homeassistant/components/uptime/translations/pl.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "step": { + "user": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + } + } + }, + "title": "Uptime" +} \ No newline at end of file diff --git a/homeassistant/components/uptime/translations/pt-BR.json b/homeassistant/components/uptime/translations/pt-BR.json index fdec46961b5..d3dddae8233 100644 --- a/homeassistant/components/uptime/translations/pt-BR.json +++ b/homeassistant/components/uptime/translations/pt-BR.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o poss\u00edvel." + "single_instance_allowed": "J\u00e1 configurado. Apenas uma configura\u00e7\u00e3o \u00e9 poss\u00edvel." }, "step": { "user": { diff --git a/homeassistant/components/uptime/translations/tr.json b/homeassistant/components/uptime/translations/tr.json new file mode 100644 index 00000000000..ed090a38398 --- /dev/null +++ b/homeassistant/components/uptime/translations/tr.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + }, + "title": "\u00c7al\u0131\u015fma S\u00fcresi" +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/translations/ca.json b/homeassistant/components/uptimerobot/translations/ca.json index 9ae97109fc1..a6ed19c11f0 100644 --- a/homeassistant/components/uptimerobot/translations/ca.json +++ b/homeassistant/components/uptimerobot/translations/ca.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_api_key": "Clau API inv\u00e0lida", + "not_main_key": "S'ha detectat un tipus de clau API incorrecta, utilitza la clau API 'principal'", "reauth_failed_matching_account": "La clau API proporcionada no correspon amb l'identificador del compte de la configuraci\u00f3 actual.", "unknown": "Error inesperat" }, @@ -17,14 +18,14 @@ "data": { "api_key": "Clau API" }, - "description": "Has de proporcionar una nova clau API de nom\u00e9s lectura d'UptimeRobot", + "description": "Has de proporcionar una nova clau API 'principal' d'UptimeRobot", "title": "Reautenticaci\u00f3 de la integraci\u00f3" }, "user": { "data": { "api_key": "Clau API" }, - "description": "Has de proporcionar una clau API de nom\u00e9s lectura d'UptimeRobot" + "description": "Has de proporcionar la clau API 'principal' d'UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/de.json b/homeassistant/components/uptimerobot/translations/de.json index 3ac63f84de2..ee2af73ea20 100644 --- a/homeassistant/components/uptimerobot/translations/de.json +++ b/homeassistant/components/uptimerobot/translations/de.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "not_main_key": "Falscher API-Schl\u00fcsseltyp erkannt, verwende den API-Hauptschl\u00fcssel", "reauth_failed_matching_account": "Der von dir angegebene API-Schl\u00fcssel stimmt nicht mit der Konto-ID f\u00fcr die vorhandene Konfiguration \u00fcberein.", "unknown": "Unerwarteter Fehler" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API-Schl\u00fcssel" }, - "description": "Du musst einen neuen schreibgesch\u00fctzten API-Schl\u00fcssel von UptimeRobot bereitstellen", + "description": "Du musst einen neuen Haupt-API-Schl\u00fcssel von UptimeRobot bereitstellen", "title": "Integration erneut authentifizieren" }, "user": { "data": { "api_key": "API-Schl\u00fcssel" }, - "description": "Du musst einen schreibgesch\u00fctzten API-Schl\u00fcssel von UptimeRobot bereitstellen" + "description": "Du musst den Haupt-API-Schl\u00fcssel von UptimeRobot bereitstellen" } } } diff --git a/homeassistant/components/uptimerobot/translations/el.json b/homeassistant/components/uptimerobot/translations/el.json index 2f15a945ab0..98034c40eee 100644 --- a/homeassistant/components/uptimerobot/translations/el.json +++ b/homeassistant/components/uptimerobot/translations/el.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "invalid_api_key": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API", + "not_main_key": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03bb\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c2 \u03ba\u03bb\u03b5\u03b9\u03b4\u03b9\u03bf\u03cd API, \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af 'main' API", "reauth_failed_matching_account": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03c4\u03bf \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7.", "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" }, diff --git a/homeassistant/components/uptimerobot/translations/et.json b/homeassistant/components/uptimerobot/translations/et.json index 9b00c2a5b44..ed2e46b7cc2 100644 --- a/homeassistant/components/uptimerobot/translations/et.json +++ b/homeassistant/components/uptimerobot/translations/et.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "\u00dchendamine nurjus", "invalid_api_key": "Vigane API v\u00f5ti", + "not_main_key": "Tuvastati vale API-v\u00f5tme t\u00fc\u00fcp, kasuta API peamist v\u00f5tit", "reauth_failed_matching_account": "Sisestatud API v\u00f5ti ei vasta olemasoleva konto ID s\u00e4tetele.", "unknown": "Ootamatu t\u00f5rge" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API v\u00f5ti" }, - "description": "Pead sisestama uue UptimeRoboti kirjutuskaitstud API-v\u00f5tme", + "description": "Pead sisestama uue UptimeRoboti peamise API-v\u00f5tme", "title": "Taastuvasta sidumine" }, "user": { "data": { "api_key": "API v\u00f5ti" }, - "description": "Pead sisestama UptimeRoboti kirjutuskaitstud API-v\u00f5tme" + "description": "Pead sisestama UptimeRoboti peamise API-v\u00f5tme" } } } diff --git a/homeassistant/components/uptimerobot/translations/fr.json b/homeassistant/components/uptimerobot/translations/fr.json index 674180a1d90..ebea3087fc7 100644 --- a/homeassistant/components/uptimerobot/translations/fr.json +++ b/homeassistant/components/uptimerobot/translations/fr.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "\u00c9chec de connexion", "invalid_api_key": "Cl\u00e9 d'API non valide", + "not_main_key": "Mauvais type de cl\u00e9 d'API d\u00e9tect\u00e9, utilisez la cl\u00e9 d'API \u00ab\u00a0principale\u00a0\u00bb", "reauth_failed_matching_account": "La cl\u00e9 API que vous avez fournie ne correspond pas \u00e0 l\u2019ID de compte pour la configuration existante.", "unknown": "Erreur inattendue" }, @@ -17,14 +18,14 @@ "data": { "api_key": "Cl\u00e9 d'API" }, - "description": "Vous devez fournir une nouvelle cl\u00e9 API en lecture seule \u00e0 partir d'Uptime Robot", + "description": "Vous devez fournir une nouvelle cl\u00e9 d'API \u00ab\u00a0principale\u00a0\u00bb \u00e0 partir d'UptimeRobot", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { "data": { "api_key": "Cl\u00e9 d'API" }, - "description": "Vous devez fournir une cl\u00e9 API en lecture seule \u00e0 partir d'Uptime Robot" + "description": "Vous devez fournir la cl\u00e9 d'API \u00ab\u00a0principale\u00a0\u00bb \u00e0 partir d'UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/hu.json b/homeassistant/components/uptimerobot/translations/hu.json index 000851093a5..d17c9bf8ac1 100644 --- a/homeassistant/components/uptimerobot/translations/hu.json +++ b/homeassistant/components/uptimerobot/translations/hu.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Nem siker\u00fclt csatlakozni", "invalid_api_key": "\u00c9rv\u00e9nytelen API-kulcs", + "not_main_key": "Nem megfelel\u0151 API-kulcst\u00edpus, haszn\u00e1lja a \"main\" API-kulcsot", "reauth_failed_matching_account": "A megadott API -kulcs nem egyezik a megl\u00e9v\u0151 konfigur\u00e1ci\u00f3 fi\u00f3kazonos\u00edt\u00f3j\u00e1val.", "unknown": "V\u00e1ratlan hiba" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API kulcs" }, - "description": "Meg kell adnia egy \u00faj, csak olvashat\u00f3 API-kulcsot az Uptime Robot-t\u00f3l", + "description": "Meg kell adnia egy \u00faj \u201ef\u0151\u201d API-kulcsot az UptimeRobott\u00f3l", "title": "Integr\u00e1ci\u00f3 \u00fajb\u00f3li hiteles\u00edt\u00e9se" }, "user": { "data": { "api_key": "API kulcs" }, - "description": "Meg kell adnia egy csak olvashat\u00f3 API-kulcsot az Uptime Robot-t\u00f3l" + "description": "Meg kell adnia a \u201ef\u0151\u201d API-kulcsot az UptimeRobott\u00f3l" } } } diff --git a/homeassistant/components/uptimerobot/translations/id.json b/homeassistant/components/uptimerobot/translations/id.json index dac2e7e2814..b92f8f74027 100644 --- a/homeassistant/components/uptimerobot/translations/id.json +++ b/homeassistant/components/uptimerobot/translations/id.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Gagal terhubung", "invalid_api_key": "Kunci API tidak valid", + "not_main_key": "Jenis kunci API yang salah terdeteksi, gunakan kunci API 'main'", "reauth_failed_matching_account": "Kunci API yang Anda berikan tidak cocok dengan ID akun untuk konfigurasi yang ada.", "unknown": "Kesalahan yang tidak diharapkan" }, @@ -17,14 +18,14 @@ "data": { "api_key": "Kunci API" }, - "description": "Anda perlu menyediakan kunci API hanya-baca yang baru dari UptimeRobot", + "description": "Anda perlu menyediakan kunci API 'main' yang baru dari UptimeRobot", "title": "Autentikasi Ulang Integrasi" }, "user": { "data": { "api_key": "Kunci API" }, - "description": "Anda perlu menyediakan kunci API hanya-baca dari UptimeRobot" + "description": "Anda perlu menyediakan kunci API 'main' dari UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/it.json b/homeassistant/components/uptimerobot/translations/it.json index 3a6c87c5ea9..4359ab682ee 100644 --- a/homeassistant/components/uptimerobot/translations/it.json +++ b/homeassistant/components/uptimerobot/translations/it.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Impossibile connettersi", "invalid_api_key": "Chiave API non valida", + "not_main_key": "Rilevato tipo di chiave API errato, utilizza la chiave API 'principale'.", "reauth_failed_matching_account": "La chiave API che hai fornito non corrisponde all'ID account per la configurazione esistente.", "unknown": "Errore imprevisto" }, @@ -17,14 +18,14 @@ "data": { "api_key": "Chiave API" }, - "description": "Devi fornire una nuova chiave API di sola lettura da UptimeRobot", + "description": "Devi fornire una nuova chiave API 'principale' da UptimeRobot", "title": "Autentica nuovamente l'integrazione" }, "user": { "data": { "api_key": "Chiave API" }, - "description": "Devi fornire una chiave API di sola lettura da UptimeRobot" + "description": "Devi fornire la chiave API 'principale' da UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/ja.json b/homeassistant/components/uptimerobot/translations/ja.json index d890780eab9..e8c66b5088b 100644 --- a/homeassistant/components/uptimerobot/translations/ja.json +++ b/homeassistant/components/uptimerobot/translations/ja.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc", + "not_main_key": "\u9593\u9055\u3063\u305fAPI\u30ad\u30fc\u30bf\u30a4\u30d7\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f\u3002 'main' API\u30ad\u30fc\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044", "reauth_failed_matching_account": "\u6307\u5b9a\u3055\u308c\u305fAPI\u30ad\u30fc\u304c\u3001\u3059\u3067\u306b\u3042\u308b\u8a2d\u5b9a\u306e\u30a2\u30ab\u30a6\u30f3\u30c8ID\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002", "unknown": "\u4e88\u671f\u3057\u306a\u3044\u30a8\u30e9\u30fc" }, diff --git a/homeassistant/components/uptimerobot/translations/nl.json b/homeassistant/components/uptimerobot/translations/nl.json index 3431a9cbfa5..d3ee1f7515a 100644 --- a/homeassistant/components/uptimerobot/translations/nl.json +++ b/homeassistant/components/uptimerobot/translations/nl.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "Ongeldige API-sleutel", + "not_main_key": "Verkeerde API sleutel gedetecteerd, gebruik de 'hoofd' API sleutel", "reauth_failed_matching_account": "De API sleutel die u heeft opgegeven komt niet overeen met de account ID voor de bestaande configuratie.", "unknown": "Onverwachte fout" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API-sleutel" }, - "description": "U moet een nieuwe alleen-lezen API-sleutel van UptimeRobot opgeven", + "description": "U moet een nieuwe 'hoofd'-API-sleutel van UptimeRobot opgeven", "title": "Verifieer de integratie opnieuw" }, "user": { "data": { "api_key": "API-sleutel" }, - "description": "U moet een alleen-lezen API-sleutel van UptimeRobot opgeven" + "description": "U moet de 'hoofd' API-sleutel van UptimeRobot opgeven" } } } diff --git a/homeassistant/components/uptimerobot/translations/no.json b/homeassistant/components/uptimerobot/translations/no.json index cbb5066a747..df7a7f8045a 100644 --- a/homeassistant/components/uptimerobot/translations/no.json +++ b/homeassistant/components/uptimerobot/translations/no.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Tilkobling mislyktes", "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "not_main_key": "Feil API-n\u00f8kkeltype oppdaget, bruk 'hoved' API-n\u00f8kkelen", "reauth_failed_matching_account": "API-n\u00f8kkelen du oppgav, samsvarer ikke med konto-IDen for eksisterende konfigurasjon.", "unknown": "Uventet feil" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API-n\u00f8kkel" }, - "description": "Du m\u00e5 levere en ny skrivebeskyttet API-n\u00f8kkel fra UptimeRobot", + "description": "Du m\u00e5 oppgi en ny 'hoved' API-n\u00f8kkel fra UptimeRobot", "title": "Godkjenne integrering p\u00e5 nytt" }, "user": { "data": { "api_key": "API-n\u00f8kkel" }, - "description": "Du m\u00e5 levere en skrivebeskyttet API-n\u00f8kkel fra UptimeRobot" + "description": "Du m\u00e5 oppgi \"hoved\" API-n\u00f8kkelen fra UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/pl.json b/homeassistant/components/uptimerobot/translations/pl.json index 18c40afec1e..d5698192e6d 100644 --- a/homeassistant/components/uptimerobot/translations/pl.json +++ b/homeassistant/components/uptimerobot/translations/pl.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_api_key": "Nieprawid\u0142owy klucz API", + "not_main_key": "Wykryto nieprawid\u0142owy typ klucza API, u\u017cyj \"g\u0142\u00f3wnego\" klucza API", "reauth_failed_matching_account": "Podany klucz API nie jest zgodny z identyfikatorem konta istniej\u0105cej konfiguracji.", "unknown": "Nieoczekiwany b\u0142\u0105d" }, @@ -17,14 +18,14 @@ "data": { "api_key": "Klucz API" }, - "description": "Musisz poda\u0107 nowy, tylko do odczytu, klucz API od Uptime Robot", + "description": "Musisz poda\u0107 nowy \"g\u0142\u00f3wny\" klucz API od Uptime Robot", "title": "Ponownie uwierzytelnij integracj\u0119" }, "user": { "data": { "api_key": "Klucz API" }, - "description": "Musisz poda\u0107 klucz API (tylko do odczytu) od Uptime Robot" + "description": "Musisz poda\u0107 \"g\u0142\u00f3wny\" klucz API od Uptime Robot" } } } diff --git a/homeassistant/components/uptimerobot/translations/pt-BR.json b/homeassistant/components/uptimerobot/translations/pt-BR.json index 0d9bea96b12..4e905f67b31 100644 --- a/homeassistant/components/uptimerobot/translations/pt-BR.json +++ b/homeassistant/components/uptimerobot/translations/pt-BR.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Falha ao conectar", "invalid_api_key": "Chave de API inv\u00e1lida", + "not_main_key": "Tipo de chave de API incorreto detectado, use a chave de API 'principal'", "reauth_failed_matching_account": "A chave de API fornecida n\u00e3o corresponde ao ID da conta da configura\u00e7\u00e3o existente.", "unknown": "Erro inesperado" }, @@ -17,14 +18,14 @@ "data": { "api_key": "Chave da API" }, - "description": "Voc\u00ea precisa fornecer uma nova chave de API somente leitura do UptimeRobot", + "description": "Voc\u00ea precisa fornecer uma nova chave de API 'principal' do UptimeRobot", "title": "Reautenticar Integra\u00e7\u00e3o" }, "user": { "data": { "api_key": "Chave da API" }, - "description": "Voc\u00ea precisa fornecer uma chave de API somente leitura do UptimeRobot" + "description": "Voc\u00ea precisa fornecer a chave de API 'principal' do UptimeRobot" } } } diff --git a/homeassistant/components/uptimerobot/translations/ru.json b/homeassistant/components/uptimerobot/translations/ru.json index fd26dbf969a..4497dc5a362 100644 --- a/homeassistant/components/uptimerobot/translations/ru.json +++ b/homeassistant/components/uptimerobot/translations/ru.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "not_main_key": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0442\u0438\u043f \u043a\u043b\u044e\u0447\u0430 API, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043b\u044e\u0447 API 'main'.", "reauth_failed_matching_account": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0443 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043b\u044f \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, @@ -17,14 +18,14 @@ "data": { "api_key": "\u041a\u043b\u044e\u0447 API" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 API UptimeRobot \u0441 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 'main' \u043a\u043b\u044e\u0447 API UptimeRobot.", "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" }, "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043b\u044e\u0447 API UptimeRobot \u0441 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 'main' \u043a\u043b\u044e\u0447 API UptimeRobot." } } } diff --git a/homeassistant/components/uptimerobot/translations/tr.json b/homeassistant/components/uptimerobot/translations/tr.json index c209afdfd8e..8f7291d57fd 100644 --- a/homeassistant/components/uptimerobot/translations/tr.json +++ b/homeassistant/components/uptimerobot/translations/tr.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "Ba\u011flanma hatas\u0131", "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", + "not_main_key": "Yanl\u0131\u015f API anahtar\u0131 t\u00fcr\u00fc alg\u0131land\u0131, 'ana' API anahtar\u0131n\u0131 kullan\u0131n", "reauth_failed_matching_account": "Sa\u011flad\u0131\u011f\u0131n\u0131z API anahtar\u0131, mevcut yap\u0131land\u0131rman\u0131n hesap kimli\u011fiyle e\u015fle\u015fmiyor.", "unknown": "Beklenmeyen hata" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API Anahtar\u0131" }, - "description": "UptimeRobot'tan yeni bir salt okunur API anahtar\u0131 sa\u011flaman\u0131z gerekiyor", + "description": "UptimeRobot'tan yeni bir 'ana' API anahtar\u0131 sa\u011flaman\u0131z gerekiyor", "title": "Entegrasyonu Yeniden Do\u011frula" }, "user": { "data": { "api_key": "API Anahtar\u0131" }, - "description": "UptimeRobot'tan salt okunur bir API anahtar\u0131 sa\u011flaman\u0131z gerekiyor" + "description": "UptimeRobot'tan 'ana' API anahtar\u0131n\u0131 sa\u011flaman\u0131z gerekiyor" } } } diff --git a/homeassistant/components/uptimerobot/translations/zh-Hant.json b/homeassistant/components/uptimerobot/translations/zh-Hant.json index 8b01cab6d7c..e8edfbf1934 100644 --- a/homeassistant/components/uptimerobot/translations/zh-Hant.json +++ b/homeassistant/components/uptimerobot/translations/zh-Hant.json @@ -9,6 +9,7 @@ "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_api_key": "API \u91d1\u9470\u7121\u6548", + "not_main_key": "\u5075\u6e2c\u5230\u932f\u8aa4\u7684 API \u91d1\u9470\u985e\u578b\u3001\u4f7f\u7528 'main' API \u91d1\u9470", "reauth_failed_matching_account": "\u6240\u63d0\u4f9b\u7684\u91d1\u9470\u8207\u73fe\u6709\u8a2d\u5b9a\u5e33\u865f ID \u4e0d\u7b26\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, @@ -17,14 +18,14 @@ "data": { "api_key": "API \u91d1\u9470" }, - "description": "\u9700\u8981\u63d0\u4f9b\u7531 UptimeRobot \u53d6\u5f97\u4e00\u7d44\u65b0\u7684\u552f\u8b80 API \u91d1\u9470", + "description": "\u5fc5\u9808\u63d0\u4f9b\u7531 UptimeRobot \u53d6\u5f97\u4e00\u7d44\u65b0\u7684 'main' API \u91d1\u9470", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, "user": { "data": { "api_key": "API \u91d1\u9470" }, - "description": "\u9700\u8981\u63d0\u4f9b\u7531 UptimeRobot \u53d6\u5f97\u552f\u8b80 API \u91d1\u9470" + "description": "\u5fc5\u9808\u63d0\u4f9b\u7531 UptimeRobot \u53d6\u5f97\u4e4b 'main' API \u91d1\u9470" } } } diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 5cb4d6f7abb..05ed01f8208 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.select import DOMAIN as SELECT_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, Platform +from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, CONF_UNIQUE_ID, Platform from homeassistant.core import HomeAssistant, split_entity_id from homeassistant.helpers import discovery, entity_registry as er import homeassistant.helpers.config_validation as cv @@ -78,6 +78,7 @@ METER_CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_SOURCE_SENSOR): cv.entity_id, vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_METER_TYPE): vol.In(METER_TYPES), vol.Optional(CONF_METER_OFFSET, default=DEFAULT_OFFSET): vol.All( cv.time_period, cv.positive_timedelta, max_28_days @@ -137,13 +138,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if not conf[CONF_TARIFFS]: # only one entity is required - name = conf.get(CONF_NAME, meter) hass.async_create_task( discovery.async_load_platform( hass, SENSOR_DOMAIN, DOMAIN, - {name: {CONF_METER: meter, CONF_NAME: name}}, + {meter: {CONF_METER: meter}}, config, ) ) @@ -169,7 +169,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: name = f"{meter} {tariff}" tariff_confs[name] = { CONF_METER: meter, - CONF_NAME: name, CONF_TARIFF: tariff, } diff --git a/homeassistant/components/utility_meter/config_flow.py b/homeassistant/components/utility_meter/config_flow.py index ed12b3038b6..cabd90ba8bd 100644 --- a/homeassistant/components/utility_meter/config_flow.py +++ b/homeassistant/components/utility_meter/config_flow.py @@ -6,7 +6,7 @@ from typing import Any, cast import voluptuous as vol -from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT +from homeassistant.const import CONF_NAME from homeassistant.helpers import selector from homeassistant.helpers.schema_config_entry_flow import ( SchemaConfigFlowHandler, @@ -34,15 +34,15 @@ from .const import ( ) METER_TYPES = [ - {"value": "none", "label": "No cycle"}, - {"value": QUARTER_HOURLY, "label": "Every 15 minutes"}, - {"value": HOURLY, "label": "Hourly"}, - {"value": DAILY, "label": "Daily"}, - {"value": WEEKLY, "label": "Weekly"}, - {"value": MONTHLY, "label": "Monthly"}, - {"value": BIMONTHLY, "label": "Every two months"}, - {"value": QUARTERLY, "label": "Quarterly"}, - {"value": YEARLY, "label": "Yearly"}, + selector.SelectOptionDict(value="none", label="No cycle"), + selector.SelectOptionDict(value=QUARTER_HOURLY, label="Every 15 minutes"), + selector.SelectOptionDict(value=HOURLY, label="Hourly"), + selector.SelectOptionDict(value=DAILY, label="Daily"), + selector.SelectOptionDict(value=WEEKLY, label="Weekly"), + selector.SelectOptionDict(value=MONTHLY, label="Monthly"), + selector.SelectOptionDict(value=BIMONTHLY, label="Every two months"), + selector.SelectOptionDict(value=QUARTERLY, label="Quarterly"), + selector.SelectOptionDict(value=YEARLY, label="Yearly"), ] @@ -58,40 +58,38 @@ def _validate_config(data: Any) -> Any: OPTIONS_SCHEMA = vol.Schema( { - vol.Required(CONF_SOURCE_SENSOR): selector.selector( - {"entity": {"domain": "sensor"}}, + vol.Required(CONF_SOURCE_SENSOR): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor"), ), } ) CONFIG_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): selector.selector({"text": {}}), - vol.Required(CONF_SOURCE_SENSOR): selector.selector( - {"entity": {"domain": "sensor"}}, + vol.Required(CONF_NAME): selector.TextSelector(), + vol.Required(CONF_SOURCE_SENSOR): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor"), ), - vol.Required(CONF_METER_TYPE): selector.selector( - {"select": {"options": METER_TYPES}} + vol.Required(CONF_METER_TYPE): selector.SelectSelector( + selector.SelectSelectorConfig(options=METER_TYPES), ), - vol.Required(CONF_METER_OFFSET, default=0): selector.selector( - { - "number": { - "min": 0, - "max": 28, - "mode": "box", - CONF_UNIT_OF_MEASUREMENT: "days", - } - } + vol.Required(CONF_METER_OFFSET, default=0): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=28, + mode=selector.NumberSelectorMode.BOX, + unit_of_measurement="days", + ), ), - vol.Required(CONF_TARIFFS, default=[]): selector.selector( - {"select": {"options": [], "custom_value": True, "multiple": True}} - ), - vol.Required(CONF_METER_NET_CONSUMPTION, default=False): selector.selector( - {"boolean": {}} - ), - vol.Required(CONF_METER_DELTA_VALUES, default=False): selector.selector( - {"boolean": {}} + vol.Required(CONF_TARIFFS, default=[]): selector.SelectSelector( + selector.SelectSelectorConfig(options=[], custom_value=True, multiple=True), ), + vol.Required( + CONF_METER_NET_CONSUMPTION, default=False + ): selector.BooleanSelector(), + vol.Required( + CONF_METER_DELTA_VALUES, default=False + ): selector.BooleanSelector(), } ) diff --git a/homeassistant/components/utility_meter/select.py b/homeassistant/components/utility_meter/select.py index 1f39b7f7c16..008a0ff6120 100644 --- a/homeassistant/components/utility_meter/select.py +++ b/homeassistant/components/utility_meter/select.py @@ -13,13 +13,19 @@ from homeassistant.components.select.const import ( SERVICE_SELECT_OPTION, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + CONF_UNIQUE_ID, + STATE_UNAVAILABLE, +) from homeassistant.core import Event, HomeAssistant, callback, split_entity_id from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ( ATTR_TARIFF, @@ -27,6 +33,7 @@ from .const import ( CONF_METER, CONF_TARIFFS, DATA_LEGACY_COMPONENT, + DATA_UTILITY, SERVICE_SELECT_NEXT_TARIFF, SERVICE_SELECT_TARIFF, TARIFF_ICON, @@ -50,16 +57,33 @@ async def async_setup_entry( async_add_entities([tariff_select]) -async def async_setup_platform(hass, conf, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistant, + conf: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up the utility meter select.""" + if discovery_info is None: + _LOGGER.error( + "This platform is not available to configure " + "from 'select:' in configuration.yaml" + ) + return + legacy_component = hass.data[DATA_LEGACY_COMPONENT] + meter: str = discovery_info[CONF_METER] + conf_meter_unique_id: str | None = hass.data[DATA_UTILITY][meter].get( + CONF_UNIQUE_ID + ) + async_add_entities( [ TariffSelect( discovery_info[CONF_METER], discovery_info[CONF_TARIFFS], legacy_component.async_add_entities, - None, + conf_meter_unique_id, ) ] ) @@ -123,7 +147,7 @@ class LegacyTariffSelect(Entity): def __init__(self, tracked_entity_id): """Initialize the entity.""" self._attr_icon = TARIFF_ICON - # Set name to influence enity_id + # Set name to influence entity_id self._attr_name = split_entity_id(tracked_entity_id)[1] self.tracked_entity_id = tracked_entity_id diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 7b1f28ff5b4..8df86b3e5a8 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -1,23 +1,27 @@ """Utility meter from sensors providing raw data.""" from __future__ import annotations +from dataclasses import dataclass from datetime import datetime, timedelta from decimal import Decimal, DecimalException, InvalidOperation import logging +from typing import Any from croniter import croniter import voluptuous as vol from homeassistant.components.sensor import ( ATTR_LAST_RESET, + RestoreSensor, SensorDeviceClass, - SensorEntity, + SensorExtraStoredData, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, + CONF_UNIQUE_ID, ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR, STATE_UNAVAILABLE, @@ -31,10 +35,10 @@ from homeassistant.helpers.event import ( async_track_point_in_time, async_track_state_change_event, ) -from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.start import async_at_start from homeassistant.helpers.template import is_number from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.util import slugify import homeassistant.util.dt as dt_util from .const import ( @@ -176,13 +180,34 @@ async def async_setup_platform( ) -> None: """Set up the utility meter sensor.""" if discovery_info is None: - _LOGGER.error("This platform is only available through discovery") + _LOGGER.error( + "This platform is not available to configure " + "from 'sensor:' in configuration.yaml" + ) return meters = [] for conf in discovery_info.values(): meter = conf[CONF_METER] conf_meter_source = hass.data[DATA_UTILITY][meter][CONF_SOURCE_SENSOR] + conf_meter_unique_id = hass.data[DATA_UTILITY][meter].get(CONF_UNIQUE_ID) + conf_sensor_tariff = conf.get(CONF_TARIFF, "single_tariff") + conf_sensor_unique_id = ( + f"{conf_meter_unique_id}_{conf_sensor_tariff}" + if conf_meter_unique_id + else None + ) + conf_meter_name = hass.data[DATA_UTILITY][meter].get(CONF_NAME, meter) + conf_sensor_tariff = conf.get(CONF_TARIFF) + + suggested_entity_id = None + if conf_sensor_tariff: + conf_sensor_name = f"{conf_meter_name} {conf_sensor_tariff}" + slug = slugify(f"{meter} {conf_sensor_tariff}") + suggested_entity_id = f"sensor.{slug}" + else: + conf_sensor_name = conf_meter_name + conf_meter_type = hass.data[DATA_UTILITY][meter].get(CONF_METER_TYPE) conf_meter_offset = hass.data[DATA_UTILITY][meter][CONF_METER_OFFSET] conf_meter_delta_values = hass.data[DATA_UTILITY][meter][ @@ -200,13 +225,14 @@ async def async_setup_platform( delta_values=conf_meter_delta_values, meter_offset=conf_meter_offset, meter_type=conf_meter_type, - name=conf.get(CONF_NAME), + name=conf_sensor_name, net_consumption=conf_meter_net_consumption, parent_meter=meter, source_entity=conf_meter_source, tariff_entity=conf_meter_tariff_entity, - tariff=conf.get(CONF_TARIFF), - unique_id=None, + tariff=conf_sensor_tariff, + unique_id=conf_sensor_unique_id, + suggested_entity_id=suggested_entity_id, ) meters.append(meter_sensor) @@ -223,7 +249,52 @@ async def async_setup_platform( ) -class UtilityMeterSensor(RestoreEntity, SensorEntity): +@dataclass +class UtilitySensorExtraStoredData(SensorExtraStoredData): + """Object to hold extra stored data.""" + + last_period: Decimal + last_reset: datetime | None + status: str + + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the utility sensor data.""" + data = super().as_dict() + data["last_period"] = str(self.last_period) + if isinstance(self.last_reset, (datetime)): + data["last_reset"] = self.last_reset.isoformat() + data["status"] = self.status + + return data + + @classmethod + def from_dict(cls, restored: dict[str, Any]) -> UtilitySensorExtraStoredData | None: + """Initialize a stored sensor state from a dict.""" + extra = SensorExtraStoredData.from_dict(restored) + if extra is None: + return None + + try: + last_period: Decimal = Decimal(restored["last_period"]) + last_reset: datetime | None = dt_util.parse_datetime(restored["last_reset"]) + status: str = restored["status"] + except KeyError: + # restored is a dict, but does not have all values + return None + except InvalidOperation: + # last_period is corrupted + return None + + return cls( + extra.native_value, + extra.native_unit_of_measurement, + last_period, + last_reset, + status, + ) + + +class UtilityMeterSensor(RestoreSensor): """Representation of an utility meter sensor.""" def __init__( @@ -240,9 +311,11 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): tariff_entity, tariff, unique_id, + suggested_entity_id=None, ): """Initialize the Utility Meter sensor.""" self._attr_unique_id = unique_id + self.entity_id = suggested_entity_id self._parent_meter = parent_meter self._sensor_source_id = source_entity self._state = None @@ -288,10 +361,15 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): sensor.start(source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)) if ( - old_state is None - or new_state is None - or old_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE] + new_state is None or new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE] + or ( + not self._sensor_delta_values + and ( + old_state is None + or old_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ) + ) ): return @@ -309,9 +387,12 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): self._state += adjustment except DecimalException as err: - _LOGGER.warning( - "Invalid state (%s > %s): %s", old_state.state, new_state.state, err - ) + if self._sensor_delta_values: + _LOGGER.warning("Invalid adjustment of %s: %s", new_state.state, err) + else: + _LOGGER.warning( + "Invalid state (%s > %s): %s", old_state.state, new_state.state, err + ) self.async_write_ha_state() @callback @@ -388,7 +469,18 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): ) ) - if state := await self.async_get_last_state(): + if (last_sensor_data := await self.async_get_last_sensor_data()) is not None: + # new introduced in 2022.04 + self._state = last_sensor_data.native_value + self._unit_of_measurement = last_sensor_data.native_unit_of_measurement + self._last_period = last_sensor_data.last_period + self._last_reset = last_sensor_data.last_reset + if last_sensor_data.status == COLLECTING: + # Null lambda to allow cancelling the collection on tariff change + self._collecting = lambda: None + + elif state := await self.async_get_last_state(): + # legacy to be removed on 2022.10 (we are keeping this to avoid utility_meter counter losses) try: self._state = Decimal(state.state) except InvalidOperation: @@ -411,7 +503,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): dt_util.parse_datetime(state.attributes.get(ATTR_LAST_RESET)) ) if state.attributes.get(ATTR_STATUS) == COLLECTING: - # Fake cancellation function to init the meter in similar state + # Null lambda to allow cancelling the collection on tariff change self._collecting = lambda: None @callback @@ -466,7 +558,7 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): @property def device_class(self): """Return the device class of the sensor.""" - return DEVICE_CLASS_MAP.get(self.unit_of_measurement) + return DEVICE_CLASS_MAP.get(self._unit_of_measurement) @property def state_class(self): @@ -515,3 +607,23 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): def icon(self): """Return the icon to use in the frontend, if any.""" return ICON + + @property + def extra_restore_state_data(self) -> UtilitySensorExtraStoredData: + """Return sensor specific state data to be restored.""" + return UtilitySensorExtraStoredData( + self.native_value, + self.native_unit_of_measurement, + self._last_period, + self._last_reset, + PAUSED if self._collecting is None else COLLECTING, + ) + + async def async_get_last_sensor_data(self) -> UtilitySensorExtraStoredData | None: + """Restore Utility Meter Sensor Extra Stored Data.""" + if (restored_last_extra_data := await self.async_get_last_extra_data()) is None: + return None + + return UtilitySensorExtraStoredData.from_dict( + restored_last_extra_data.as_dict() + ) diff --git a/homeassistant/components/utility_meter/services.yaml b/homeassistant/components/utility_meter/services.yaml index 39f0c9056b5..fc1e08b153a 100644 --- a/homeassistant/components/utility_meter/services.yaml +++ b/homeassistant/components/utility_meter/services.yaml @@ -2,7 +2,7 @@ reset: name: Reset - description: Resets all counters of an utility meter. + description: Resets all counters of a utility meter. target: entity: domain: select @@ -35,7 +35,6 @@ calibrate: target: entity: domain: sensor - integration: utility_meter fields: value: name: Value diff --git a/homeassistant/components/utility_meter/translations/bg.json b/homeassistant/components/utility_meter/translations/bg.json new file mode 100644 index 00000000000..35cfa0ad1d7 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/ca.json b/homeassistant/components/utility_meter/translations/ca.json new file mode 100644 index 00000000000..6781fc9533c --- /dev/null +++ b/homeassistant/components/utility_meter/translations/ca.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Cicle de reinici del comptador", + "delta_values": "Valors delta", + "name": "Nom", + "net_consumption": "Consum net", + "offset": "Despla\u00e7ament del reinici del comptador", + "source": "Sensor d'entrada", + "tariffs": "Tarifes suportades" + }, + "data_description": { + "delta_values": "Activa-ho si els les lectures s\u00f3n valors delta (actual - anterior) en lloc de valors absoluts.", + "net_consumption": "Activa-ho si \u00e9s un comptador net, \u00e9s a dir, pot augmentar i disminuir.", + "offset": "Despla\u00e7a el dia de restabliment mensual del comptador.", + "tariffs": "Llista de tarifes admeses, deixa-la en blanc si utilitzes una \u00fanica tarifa." + }, + "description": "El sensor de comptador permet fer un seguiment dels consums de diversos serveis (per exemple, energia, gas, aigua o calefacci\u00f3) durant un per\u00edode de temps establert, normalment mensual. El sensor comptador tamb\u00e9 permet dividir el consum per tarifes; en aquest cas, es crear\u00e0 un sensor per a cada tarifa i una entitat de selecci\u00f3 que tria la tarifa actual.", + "title": "Afegeix comptador" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sensor d'entrada" + } + } + } + }, + "title": "Comptador" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/cs.json b/homeassistant/components/utility_meter/translations/cs.json new file mode 100644 index 00000000000..552ce790742 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/cs.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Cyklus vynulov\u00e1n\u00ed m\u011b\u0159i\u010de", + "delta_values": "Rozd\u00edlov\u00e9 hodnoty", + "name": "Jm\u00e9no", + "net_consumption": "\u010cist\u00e1 spot\u0159eba", + "offset": "Posun vynulov\u00e1n\u00ed m\u011b\u0159i\u010de", + "source": "Vstupn\u00ed senzor", + "tariffs": "Podporovan\u00e9 tarify" + }, + "data_description": { + "delta_values": "Povolte, pokud jsou zdrojov\u00e9 hodnoty rozd\u00edlov\u00e9 hodnoty od posledn\u00edho ode\u010dtu nam\u00edsto absolutn\u00edch hodnot.", + "net_consumption": "Povolte, pokud je zdroj \u010dist\u00fdm m\u011b\u0159i\u010dem, co\u017e znamen\u00e1, \u017ee se m\u016f\u017ee zvy\u0161ovat i sni\u017eovat.", + "offset": "Posunut\u00ed dne m\u011bs\u00ed\u010dn\u00edho vynulov\u00e1n\u00ed m\u011b\u0159i\u010de.", + "tariffs": "Seznam podporovan\u00fdch tarif\u016f, pokud je pot\u0159eba pouze jeden tarif, nechte pr\u00e1zdn\u00e9." + }, + "description": "Vytvo\u0159\u00ed senzor, kter\u00fd sleduje spot\u0159ebu r\u016fzn\u00fdch zdroj\u016f (nap\u0159. energie, plynu, vody, vyt\u00e1p\u011bn\u00ed) za nastaven\u00e9 \u010dasov\u00e9 obdob\u00ed, obvykle m\u011bs\u00ed\u010dn\u011b. Senzor m\u011b\u0159i\u010de m\u00e9di\u00ed voliteln\u011b podporuje rozd\u011blen\u00ed spot\u0159eby podle tarif\u016f, v takov\u00e9m p\u0159\u00edpad\u011b se vytvo\u0159\u00ed jeden senzor pro ka\u017ed\u00fd tarif a tak\u00e9 v\u00fdb\u011brov\u00e1 entita pro v\u00fdb\u011br aktu\u00e1ln\u00edho tarifu.", + "title": "P\u0159idat m\u011b\u0159i\u010d" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Vstupn\u00ed senzor" + } + } + } + }, + "title": "M\u011b\u0159i\u010d" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/de.json b/homeassistant/components/utility_meter/translations/de.json new file mode 100644 index 00000000000..870772e0dbe --- /dev/null +++ b/homeassistant/components/utility_meter/translations/de.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Z\u00e4hler-Reset-Zyklus", + "delta_values": "Delta-Werte", + "name": "Name", + "net_consumption": "Netzverbrauch", + "offset": "Z\u00e4hler-Reset-Offset", + "source": "Eingangssensor", + "tariffs": "Unterst\u00fctzte Tarife" + }, + "data_description": { + "delta_values": "Aktiviere diese Option, wenn die Quellwerte Deltawerte seit dem letzten Lesen anstelle von absoluten Werten sind.", + "net_consumption": "Aktiviere diese Option, wenn die Quelle ein Nettoz\u00e4hler ist, was bedeutet, dass sie sowohl steigen als auch fallen kann.", + "offset": "Versetzen des Tages einer monatlichen Z\u00e4hlerr\u00fccksetzung.", + "tariffs": "Eine Liste der unterst\u00fctzten Tarife; leer lassen, wenn nur ein einziger Tarif ben\u00f6tigt wird." + }, + "description": "Erstelle einen Sensor, der den Verbrauch verschiedener Versorgungsleistungen (z. B. Energie, Gas, Wasser, Heizung) \u00fcber einen konfigurierten Zeitraum, in der Regel monatlich, erfasst. Der Sensor f\u00fcr den Verbrauchsz\u00e4hler unterst\u00fctzt optional die Aufteilung des Verbrauchs nach Tarifen. In diesem Fall wird ein Sensor f\u00fcr jeden Tarif sowie eine Auswahlm\u00f6glichkeit zur Auswahl des aktuellen Tarifs erstellt.", + "title": "Verbrauchsz\u00e4hler hinzuf\u00fcgen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Eingangssensor" + } + } + } + }, + "title": "Verbrauchsz\u00e4hler" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/el.json b/homeassistant/components/utility_meter/translations/el.json new file mode 100644 index 00000000000..3264503bab9 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "\u039a\u03cd\u03ba\u03bb\u03bf\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae", + "delta_values": "\u03a4\u03b9\u03bc\u03ad\u03c2 \u0394\u03ad\u03bb\u03c4\u03b1", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1", + "net_consumption": "\u039a\u03b1\u03b8\u03b1\u03c1\u03ae \u03ba\u03b1\u03c4\u03b1\u03bd\u03ac\u03bb\u03c9\u03c3\u03b7", + "offset": "\u039c\u03b5\u03c4\u03b1\u03c4\u03cc\u03c0\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae", + "source": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5", + "tariffs": "\u03a5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03b1" + }, + "data_description": { + "delta_values": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03ac\u03bd \u03bf\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03c0\u03b7\u03b3\u03ae\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b4\u03ad\u03bb\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03b1\u03bd\u03c4\u03af \u03b3\u03b9\u03b1 \u03b1\u03c0\u03cc\u03bb\u03c5\u03c4\u03b5\u03c2 \u03c4\u03b9\u03bc\u03ad\u03c2.", + "net_consumption": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b5\u03ac\u03bd \u03b7 \u03c0\u03b7\u03b3\u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03b8\u03b1\u03c1\u03cc\u03c2 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae\u03c2, \u03b4\u03b7\u03bb\u03b1\u03b4\u03ae \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03c4\u03cc\u03c3\u03bf \u03bd\u03b1 \u03b1\u03c5\u03be\u03ac\u03bd\u03b5\u03c4\u03b1\u03b9 \u03cc\u03c3\u03bf \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03bc\u03b5\u03b9\u03ce\u03bd\u03b5\u03c4\u03b1\u03b9.", + "offset": "\u0391\u03bd\u03c4\u03b9\u03c3\u03c4\u03ac\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bc\u03ad\u03c1\u03b1\u03c2 \u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03b1\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae.", + "tariffs": "\u039c\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b1 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03b1, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03ae \u03b5\u03ac\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf." + }, + "description": "\u039f \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae \u03ba\u03bf\u03b9\u03bd\u03ae\u03c2 \u03c9\u03c6\u03ad\u03bb\u03b5\u03b9\u03b1\u03c2 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03b9 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7\u03c2 \u03c4\u03c9\u03bd \u03ba\u03b1\u03c4\u03b1\u03bd\u03b1\u03bb\u03ce\u03c3\u03b5\u03c9\u03bd \u03b4\u03b9\u03b1\u03c6\u03cc\u03c1\u03c9\u03bd \u03b2\u03bf\u03b7\u03b8\u03b7\u03c4\u03b9\u03ba\u03ce\u03bd \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03b9\u03ce\u03bd (\u03c0.\u03c7. \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1, \u03c6\u03c5\u03c3\u03b9\u03ba\u03cc \u03b1\u03ad\u03c1\u03b9\u03bf, \u03bd\u03b5\u03c1\u03cc, \u03b8\u03ad\u03c1\u03bc\u03b1\u03bd\u03c3\u03b7) \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03bf\u03c1\u03c6\u03c9\u03bc\u03ad\u03bd\u03b7 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ae \u03c0\u03b5\u03c1\u03af\u03bf\u03b4\u03bf, \u03c3\u03c5\u03bd\u03ae\u03b8\u03c9\u03c2 \u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03b1. \u039f \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03c4\u03bf\u03c5 \u03b2\u03bf\u03b7\u03b8\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03cc \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03b1\u03bd\u03ac\u03bb\u03c9\u03c3\u03b7\u03c2 \u03b1\u03bd\u03ac \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03b1.\n \u0397 \u03bc\u03b5\u03c4\u03b1\u03c4\u03cc\u03c0\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c4\u03b7 \u03bc\u03b5\u03c4\u03b1\u03c4\u03cc\u03c0\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b7\u03bc\u03ad\u03c1\u03b1\u03c2 \u03c4\u03b7\u03c2 \u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03b1\u03c2 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae.\n \u03a4\u03b1 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b1 \u03bc\u03b5 \u03ba\u03cc\u03bc\u03bc\u03b1\u03c4\u03b1, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03ae \u03b5\u03ac\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03ad\u03bd\u03b1 \u03c4\u03b9\u03bc\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf.", + "title": "\u039d\u03ad\u03bf\u03c2 \u03b2\u03bf\u03b7\u03b8\u03b7\u03c4\u03b9\u03ba\u03cc\u03c2 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae\u03c2" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b1\u03c2 \u03b5\u03b9\u03c3\u03cc\u03b4\u03bf\u03c5" + } + } + } + }, + "title": "\u039c\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae\u03c2 \u03ba\u03bf\u03b9\u03bd\u03ae\u03c2 \u03c9\u03c6\u03ad\u03bb\u03b5\u03b9\u03b1\u03c2" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/et.json b/homeassistant/components/utility_meter/translations/et.json new file mode 100644 index 00000000000..579933130fb --- /dev/null +++ b/homeassistant/components/utility_meter/translations/et.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "M\u00f5\u00f5turi l\u00e4htestamise ts\u00fckkel", + "delta_values": "Delta v\u00e4\u00e4rtused", + "name": "Nimi", + "net_consumption": "Netotarbimine", + "offset": "Arvesti l\u00e4htestamise nihe", + "source": "Sisendandur", + "tariffs": "Toetatud tariifid" + }, + "data_description": { + "delta_values": "Luba kui l\u00e4htev\u00e4\u00e4rtused on absoluutv\u00e4\u00e4rtuste asemel delta v\u00e4\u00e4rtused alates viimasest lugemisest.", + "net_consumption": "Luba, kui allikaks on netoarvesti, mis t\u00e4hendab, et see v\u00f5ib nii suureneda kui ka v\u00e4heneda.", + "offset": "Arvesti igakuise l\u00e4htestamise p\u00e4eva nihutamine.", + "tariffs": "Toetatud tariifide loend, j\u00e4ta t\u00fchjaks kui vajad ainult \u00fchte tariifi." + }, + "description": "Loo andur mis j\u00e4lgib erinevate kommunaalteenuste (nt energia, gaas, vesi, k\u00fcte) tarbimist seadistatud aja jooksul, tavaliselt kord kuus. Kommunaalarvesti andur toetab valikuliselt tarbimise jagamist tariifide kaupa, sellisel juhul luuakse iga tariifi kohta \u00fcks andur ning ka valitud olem kehtiva tariifi valimiseks.", + "title": "Lisa kommunaalm\u00f5\u00f5tja" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sisendandur" + } + } + } + }, + "title": "Kommunaalarvesti" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/fr.json b/homeassistant/components/utility_meter/translations/fr.json new file mode 100644 index 00000000000..9659cf413bb --- /dev/null +++ b/homeassistant/components/utility_meter/translations/fr.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Cycle de remise \u00e0 z\u00e9ro du compteur", + "delta_values": "Valeurs delta", + "name": "Nom", + "net_consumption": "Consommation nette", + "offset": "D\u00e9calage de la remise \u00e0 z\u00e9ro du compteur", + "source": "Capteur d'entr\u00e9e", + "tariffs": "Tarifs pris en charge" + }, + "data_description": { + "delta_values": "Activer si les valeurs de la source sont des valeurs delta depuis la derni\u00e8re lecture au lieu de valeurs absolues.", + "net_consumption": "Activer si la source est un compteur net, c'est-\u00e0-dire qu'il peut \u00e0 la fois augmenter et diminuer.", + "offset": "D\u00e9calage du jour de r\u00e9initialisation mensuelle du compteur.", + "tariffs": "Liste des tarifs pris en charge\u00a0; laisser vide si un seul tarif est n\u00e9cessaire." + }, + "description": "Cr\u00e9ez un capteur permettant de suivre les consommations de divers services publics (comme l'\u00e9nergie, le gaz, l'eau ou le chauffage) sur une p\u00e9riode configur\u00e9e, g\u00e9n\u00e9ralement mensuelle. Le capteur de compteur de services publics peut, si n\u00e9cessaire, r\u00e9partir la consommation par tarifs, auquel cas un capteur sera cr\u00e9\u00e9 pour chaque tarif ainsi qu'une entit\u00e9 de s\u00e9lection permettant de choisir le tarif actuel.", + "title": "Ajouter un compteur de services publics (eau, gaz, \u00e9lectricit\u00e9\u2026)" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Capteur d'entr\u00e9e" + } + } + } + }, + "title": "Compteur de services publics (eau, gaz, \u00e9lectricit\u00e9\u2026)" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/hu.json b/homeassistant/components/utility_meter/translations/hu.json new file mode 100644 index 00000000000..646517cfd24 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/hu.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "M\u00e9r\u0151 null\u00e1z\u00e1si ciklusa", + "delta_values": "Delta \u00e9rt\u00e9kek", + "name": "Elnevez\u00e9s", + "net_consumption": "Nett\u00f3 fogyaszt\u00e1s", + "offset": "M\u00e9r\u0151 null\u00e1z\u00e1s\u00e1nak eltol\u00e1sa", + "source": "Forr\u00e1s \u00e9rz\u00e9kel\u0151", + "tariffs": "T\u00e1mogatott tarif\u00e1k" + }, + "data_description": { + "delta_values": "Akkor kapcsolja be, ha az \u00e9rt\u00e9kek az utols\u00f3 olvasat \u00f3ta k\u00fcl\u00f6nb\u00f6zeti \u00e9rt\u00e9kek, nem pedig abszol\u00fat \u00e9rt\u00e9kek.", + "net_consumption": "Enged\u00e9lyezze, ha a forr\u00e1s nett\u00f3 m\u00e9r\u0151, ami azt jelenti, hogy n\u00f6vekedhet \u00e9s cs\u00f6kkenhet is.", + "offset": "A havi m\u00e9r\u0151-vissza\u00e1ll\u00edt\u00e1s napj\u00e1nak eltol\u00e1sa.", + "tariffs": "A t\u00e1mogatott tarif\u00e1k list\u00e1ja, hagyja \u00fcresen, ha csak egyetlen tarif\u00e1ra van sz\u00fcks\u00e9g." + }, + "description": "A k\u00f6z\u00fczemi fogyaszt\u00e1sm\u00e9r\u0151 \u00e9rz\u00e9kel\u0151 lehet\u0151s\u00e9get ad a k\u00fcl\u00f6nf\u00e9le k\u00f6zm\u0171vek (pl. energia, g\u00e1z, v\u00edz, f\u0171t\u00e9s) fogyaszt\u00e1s\u00e1nak nyomon k\u00f6vet\u00e9s\u00e9re egy konfigur\u00e1lt id\u0151tartamon kereszt\u00fcl, jellemz\u0151en havonta. Az \u00e9rz\u00e9kel\u0151 a fogyaszt\u00e1s tarif\u00e1k szerinti megoszt\u00e1s\u00e1t is t\u00e1mogatja.", + "title": "\u00daj k\u00f6z\u00fczemi m\u00e9r\u0151" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Forr\u00e1s \u00e9rz\u00e9kel\u0151" + } + } + } + }, + "title": "K\u00f6z\u00fczemi fogyaszt\u00e1sm\u00e9r\u0151" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/id.json b/homeassistant/components/utility_meter/translations/id.json new file mode 100644 index 00000000000..d2f9bab72c5 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Siklus setel ulang meter", + "delta_values": "Nilai delta", + "name": "Nama", + "net_consumption": "Konsumsi netto", + "offset": "Offset setel ulang meter", + "source": "Sensor input", + "tariffs": "Tarif yang didukung" + }, + "data_description": { + "delta_values": "Aktifkan jika nilai sumber adalah nilai delta sejak pembacaan terakhir, bukan nilai absolut.", + "net_consumption": "Aktifkan jika sumbernya adalah ukuran netto, artinya bisa bertambah dan berkurang.", + "offset": "Ofset hari setelah penyetelan ulang bulanan", + "tariffs": "Daftar tarif yang didukung, kosongkan jika hanya diperlukan satu tarif." + }, + "description": "Buat sensor yang melacak konsumsi berbagai utilitas (misalnya, energi, gas, air, pemanas) selama periode waktu yang dikonfigurasi, biasanya bulanan. Sensor meter utilitas secara opsional mendukung pemisahan konsumsi berdasarkan tarif, dalam hal ini satu sensor untuk setiap tarif dibuat serta entitas terpilih untuk memilih tarif saat ini.", + "title": "Tambahkan Meter Utilitas" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sensor Input" + } + } + } + }, + "title": "Meter Utilitas" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/it.json b/homeassistant/components/utility_meter/translations/it.json new file mode 100644 index 00000000000..03512cc0b7a --- /dev/null +++ b/homeassistant/components/utility_meter/translations/it.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Ciclo di ripristino del contatore", + "delta_values": "Valori delta", + "name": "Nome", + "net_consumption": "Consumo netto", + "offset": "Offset azzeramento contatore", + "source": "Sensore di ingresso", + "tariffs": "Tariffe supportate" + }, + "data_description": { + "delta_values": "Abilita se i valori di origine sono valori delta dall'ultima lettura anzich\u00e9 valori assoluti.", + "net_consumption": "Abilita se la sorgente \u00e8 un contatore netto, il che significa che pu\u00f2 sia aumentare che diminuire.", + "offset": "Offset del giorno di un ripristino mensile del contatore.", + "tariffs": "Un elenco di tariffe supportate, lascia vuoto se \u00e8 necessaria una sola tariffa." + }, + "description": "Crea un sensore che tenga traccia del consumo di varie utenze (ad es. energia, gas, acqua, riscaldamento) in un periodo di tempo configurato, generalmente mensile. Il sensore del contatore di utenze supporta opzionalmente la suddivisione del consumo per tariffe, in tal caso viene creato un sensore per ciascuna tariffa e un'entit\u00e0 selezionata per scegliere la tariffa corrente.", + "title": "Aggiungi misuratore di utilit\u00e0" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sensore di ingresso" + } + } + } + }, + "title": "Misuratore di utilit\u00e0" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/ja.json b/homeassistant/components/utility_meter/translations/ja.json new file mode 100644 index 00000000000..90c175f16ec --- /dev/null +++ b/homeassistant/components/utility_meter/translations/ja.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "\u30e1\u30fc\u30bf\u30fc\u30ea\u30bb\u30c3\u30c8\u306e\u30b5\u30a4\u30af\u30eb", + "delta_values": "\u30c7\u30eb\u30bf\u5024", + "name": "\u540d\u524d", + "net_consumption": "\u7d14\u6d88\u8cbb\u91cf", + "offset": "\u30e1\u30fc\u30bf\u30fc\u30ea\u30bb\u30c3\u30c8\u30aa\u30d5\u30bb\u30c3\u30c8", + "source": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc", + "tariffs": "\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u95a2\u7a0e(Tariffs)" + }, + "data_description": { + "delta_values": "\u30bd\u30fc\u30b9\u5024\u304c\u7d76\u5bfe\u5024\u3067\u306f\u306a\u304f\u3001\u6700\u5f8c\u306e\u8aad\u307f\u53d6\u308a\u4ee5\u964d\u304c\u30c7\u30eb\u30bf\u5024\u3067\u3042\u308b\u5834\u5408\u306f\u3001\u6709\u52b9\u306b\u3057\u307e\u3059\u3002", + "net_consumption": "\u30bd\u30fc\u30b9\u304c\u30cd\u30c3\u30c8\u30e1\u30fc\u30bf(net meter)\u3001\u3064\u307e\u308a\u5897\u52a0\u3082\u3059\u308b\u3057\u6e1b\u5c11\u3082\u3059\u308b\u5834\u5408\u306f\u3001\u6709\u52b9\u306b\u3057\u307e\u3059\u3002", + "offset": "\u6bce\u6708\u306e\u30e1\u30fc\u30bf\u30fc\u30ea\u30bb\u30c3\u30c8\u306e\u65e5\u3092\u30aa\u30d5\u30bb\u30c3\u30c8\u3057\u307e\u3059\u3002", + "tariffs": "\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u6599\u91d1(Tariff)\u306e\u30ea\u30b9\u30c8\u3002\u5358\u4e00\u306e\u6599\u91d1\u8868\u306e\u307f\u304c\u5fc5\u8981\u306a\u5834\u5408\u306f\u7a7a\u306e\u307e\u307e\u306b\u3057\u307e\u3059\u3002" + }, + "description": "\u8a2d\u5b9a\u3055\u308c\u305f\u671f\u9593\u3001\u901a\u5e38\u306f\u6bce\u6708\u3001\u69d8\u3005\u306a\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3(\u30a8\u30cd\u30eb\u30ae\u30fc\u3001\u30ac\u30b9\u3001\u6c34\u3001\u6696\u623f\u306a\u3069)\u306e\u6d88\u8cbb\u91cf\u3092\u8ffd\u8de1\u3059\u308b\u30bb\u30f3\u30b5\u30fc\u3092\u4f5c\u6210\u3057\u307e\u3059\u3002\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30aa\u30d7\u30b7\u30e7\u30f3\u3068\u3057\u3066\u3001\u6599\u91d1\u8868\u306b\u3088\u308b\u6d88\u8cbb\u91cf\u306e\u5206\u5272\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u307e\u3059\u3002\u3053\u306e\u5834\u5408\u3001\u5404\u6599\u91d1\u8868\u306e\u305f\u3081\u306e1\u3064\u306e\u30bb\u30f3\u30b5\u30fc\u3068\u3001\u73fe\u5728\u306e\u6599\u91d1(Tariff)\u3092\u9078\u629e\u3059\u308b\u305f\u3081\u306e\u9078\u629e\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u304c\u4f5c\u6210\u3055\u308c\u307e\u3059\u3002", + "title": "\u65b0\u3057\u3044\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3\u30e1\u30fc\u30bf\u30fc" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "\u5165\u529b\u30bb\u30f3\u30b5\u30fc" + } + } + } + }, + "title": "\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3\u30e1\u30fc\u30bf\u30fc" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/nl.json b/homeassistant/components/utility_meter/translations/nl.json new file mode 100644 index 00000000000..8e1d986ac6b --- /dev/null +++ b/homeassistant/components/utility_meter/translations/nl.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Meter reset cyclus", + "delta_values": "Deltawaarden", + "name": "Name", + "net_consumption": "Netto verbruik", + "offset": "Meter reset offset", + "source": "Invoer sensor", + "tariffs": "Ondersteunde tarieven" + }, + "data_description": { + "delta_values": "Schakel in als de bronwaarden deltawaarden zijn sinds de laatste meting in plaats van absolute waarden.", + "net_consumption": "Schakel in als de bron een nettometer is, wat betekent dat deze zowel kan toenemen als afnemen.", + "offset": "Compenseer de dag van een maandelijkse meterreset.", + "tariffs": "Een lijst met ondersteunde tarieven, laat leeg als er maar \u00e9\u00e9n tarief nodig is." + }, + "description": "Cre\u00eber een sensor die het verbruik van verschillende nutsvoorzieningen (bv. energie, gas, water, verwarming) over een geconfigureerde tijdsperiode bijhoudt, meestal maandelijks. De sensor voor de meter voor nutsvoorzieningen ondersteunt optioneel het opsplitsen van het verbruik in tarieven, in dat geval wordt een sensor voor elk tarief aangemaakt, evenals een selectie-entiteit om het huidige tarief te kiezen.", + "title": "Nutsmeter toevoegen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Invoer sensor" + } + } + } + }, + "title": "Nutsmeter" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/no.json b/homeassistant/components/utility_meter/translations/no.json new file mode 100644 index 00000000000..05218c7010b --- /dev/null +++ b/homeassistant/components/utility_meter/translations/no.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Syklus for tilbakestilling av m\u00e5ler", + "delta_values": "Delta-verdier", + "name": "Navn", + "net_consumption": "Netto forbruk", + "offset": "Forskyvning for tilbakestilling av meter", + "source": "Inngangssensor", + "tariffs": "St\u00f8ttede tariffer" + }, + "data_description": { + "delta_values": "Aktiver hvis kildeverdiene er deltaverdier siden siste lesing i stedet for absolutte verdier.", + "net_consumption": "Aktiver hvis kilden er en nettom\u00e5ler, noe som betyr at den b\u00e5de kan \u00f8ke og redusere.", + "offset": "Forskyv dagen for en m\u00e5nedlig tilbakestilling av m\u00e5leren.", + "tariffs": "En liste over st\u00f8ttede tariffer, la st\u00e5 tom hvis bare en enkelt tariff er n\u00f8dvendig." + }, + "description": "Lag en sensor som sporer forbruket til ulike verkt\u00f8y (f.eks. energi, gass, vann, oppvarming) over en konfigurert tidsperiode, vanligvis m\u00e5nedlig. M\u00e5lersensoren st\u00f8tter valgfritt \u00e5 dele forbruket etter tariffer, i s\u00e5 fall opprettes en sensor for hver tariff samt en valgt enhet for \u00e5 velge gjeldende tariff.", + "title": "Legg til verkt\u00f8ym\u00e5ler" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Inngangssensor" + } + } + } + }, + "title": "Verkt\u00f8ym\u00e5ler" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/pl.json b/homeassistant/components/utility_meter/translations/pl.json new file mode 100644 index 00000000000..9be6f68384b --- /dev/null +++ b/homeassistant/components/utility_meter/translations/pl.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Cykl resetowania licznika", + "delta_values": "Warto\u015bci delta", + "name": "Nazwa", + "net_consumption": "Zu\u017cycie netto", + "offset": "Przesuni\u0119cie resetowania licznika", + "source": "Sensor wej\u015bciowy", + "tariffs": "Obs\u0142ugiwane taryfy" + }, + "data_description": { + "delta_values": "W\u0142\u0105cz, je\u015bli warto\u015bci \u017ar\u00f3d\u0142owe s\u0105 warto\u015bciami delta od ostatniego odczytu, a nie warto\u015bciami bezwzgl\u0119dnymi.", + "net_consumption": "W\u0142\u0105cz, je\u015bli \u017ar\u00f3d\u0142em jest licznik netto, co oznacza, \u017ce jego warto\u015bci mog\u0105 si\u0119 zar\u00f3wno zwi\u0119ksza\u0107 jak i zmniejsza\u0107.", + "offset": "Przesuni\u0119cie dnia miesi\u0119cznego zerowania licznika.", + "tariffs": "Lista obs\u0142ugiwanych taryf. Pozostaw puste, je\u015bli potrzebna jest tylko jedna taryfa." + }, + "description": "Utw\u00f3rz sensor, kt\u00f3ry \u015bledzi u\u017cycie r\u00f3\u017cnych medi\u00f3w (np. energii, gazu, wody, ogrzewania) przez skonfigurowany okres czasu, zwykle co miesi\u0105c. Sensor licznika medi\u00f3w opcjonalnie obs\u0142uguje podzia\u0142 u\u017cycia wed\u0142ug taryf, w takim przypadku tworzony jest jeden czujnik dla ka\u017cdej taryfy oraz encja do wyboru aktualnej taryfy.", + "title": "Dodaj licznik medi\u00f3w" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sensor wej\u015bciowy" + } + } + } + }, + "title": "Licznik medi\u00f3w" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/pt-BR.json b/homeassistant/components/utility_meter/translations/pt-BR.json new file mode 100644 index 00000000000..856360b05b2 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/pt-BR.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Ciclo de reinicializa\u00e7\u00e3o do medidor", + "delta_values": "Valores delta", + "name": "Nome", + "net_consumption": "Consumo l\u00edquido", + "offset": "Compensa\u00e7\u00e3o do reajuste do medidor", + "source": "Sensor de entrada", + "tariffs": "Tarifas compat\u00edveis" + }, + "data_description": { + "delta_values": "Habilite se os valores de origem forem valores delta desde a \u00faltima leitura em vez de valores absolutos.", + "net_consumption": "Ative se a fonte for um medidor de rede, o que significa que pode aumentar e diminuir.", + "offset": "Desloque o dia de uma reinicializa\u00e7\u00e3o mensal do medidor.", + "tariffs": "Uma lista de tarifas suportadas, deixe em branco se apenas uma \u00fanica tarifa for necess\u00e1ria." + }, + "description": "Crie um sensor que rastreie o consumo de v\u00e1rias utilidades (por exemplo, energia, g\u00e1s, \u00e1gua, aquecimento) durante um per\u00edodo de tempo configurado, normalmente mensalmente. O sensor do contador da concession\u00e1ria suporta opcionalmente a divis\u00e3o do consumo por tarif\u00e1rio, neste caso \u00e9 criado um sensor para cada tarif\u00e1rio e uma entidade seleccionada para escolher o tarif\u00e1rio atual.", + "title": "Adicionar medidor de utilidades" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Sensor de entrada" + } + } + } + }, + "title": "Medidor de utilidade" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/ru.json b/homeassistant/components/utility_meter/translations/ru.json new file mode 100644 index 00000000000..3eda01a8116 --- /dev/null +++ b/homeassistant/components/utility_meter/translations/ru.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "\u0426\u0438\u043a\u043b \u0441\u0431\u0440\u043e\u0441\u0430 \u0441\u0447\u0435\u0442\u0447\u0438\u043a\u0430", + "delta_values": "\u0414\u0435\u043b\u044c\u0442\u0430-\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "net_consumption": "\u0427\u0438\u0441\u0442\u043e\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u0435", + "offset": "\u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0441\u0431\u0440\u043e\u0441\u0430 \u0441\u0447\u0435\u0442\u0447\u0438\u043a\u0430", + "source": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440", + "tariffs": "\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0442\u0430\u0440\u0438\u0444\u044b" + }, + "data_description": { + "delta_values": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0435, \u0435\u0441\u043b\u0438 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0435\u043b\u044c\u0442\u0430-\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0441 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0447\u0442\u0435\u043d\u0438\u044f, \u0430 \u043d\u0435 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u043c\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438.", + "net_consumption": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0435, \u0435\u0441\u043b\u0438 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435\u0442\u0442\u043e-\u0441\u0447\u0435\u0442\u0447\u0438\u043a\u043e\u043c, \u0442\u043e \u0435\u0441\u0442\u044c \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u043a\u0430\u043a \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0442\u044c\u0441\u044f, \u0442\u0430\u043a \u0438 \u0443\u043c\u0435\u043d\u044c\u0448\u0430\u0442\u044c\u0441\u044f.", + "offset": "\u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0434\u043d\u044f \u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e\u0433\u043e \u043e\u0431\u043d\u0443\u043b\u0435\u043d\u0438\u044f \u0441\u0447\u0435\u0442\u0447\u0438\u043a\u0430.", + "tariffs": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0442\u0430\u0440\u0438\u0444\u043e\u0432, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u043d\u0443\u0436\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u0442\u0430\u0440\u0438\u0444." + }, + "description": "\u0421\u0435\u043d\u0441\u043e\u0440 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043e\u043c\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u043b\u0443\u0433 (\u044d\u043d\u0435\u0440\u0433\u0438\u0438, \u0433\u0430\u0437\u0430, \u0432\u043e\u0434\u044b, \u043e\u0442\u043e\u043f\u043b\u0435\u043d\u0438\u044f) \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043e\u0431\u044b\u0447\u043d\u043e \u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u044f \u043f\u043e \u0442\u0430\u0440\u0438\u0444\u0430\u043c, \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0430\u0440\u0438\u0444\u0430, \u0430 \u0442\u0430\u043a \u0436\u0435 \u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0442\u0430\u0440\u0438\u0444\u0430.", + "title": "\u0421\u0447\u0435\u0442\u0447\u0438\u043a \u043a\u043e\u043c\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u043b\u0443\u0433" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0441\u0435\u043d\u0441\u043e\u0440" + } + } + } + }, + "title": "\u0421\u0447\u0435\u0442\u0447\u0438\u043a \u043a\u043e\u043c\u043c\u0443\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u043b\u0443\u0433" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/tr.json b/homeassistant/components/utility_meter/translations/tr.json new file mode 100644 index 00000000000..0eedf15d93e --- /dev/null +++ b/homeassistant/components/utility_meter/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "Saya\u00e7 s\u0131f\u0131rlama d\u00f6ng\u00fcs\u00fc", + "delta_values": "Delta de\u011ferleri", + "name": "Ad", + "net_consumption": "Net t\u00fcketim", + "offset": "Saya\u00e7 s\u0131f\u0131rlama uzakl\u0131\u011f\u0131", + "source": "Giri\u015f sens\u00f6r\u00fc", + "tariffs": "Desteklenen tarifeler" + }, + "data_description": { + "delta_values": "Kaynak de\u011ferler, mutlak de\u011ferler yerine son okumadan itibaren delta de\u011ferler ise etkinle\u015ftirin.", + "net_consumption": "Kaynak bir net saya\u00e7 ise etkinle\u015ftirin, yani hem artabilir hem de azalabilir.", + "offset": "Ayl\u0131k saya\u00e7 s\u0131f\u0131rlama g\u00fcn\u00fcn\u00fc dengeleyin.", + "tariffs": "Desteklenen tarifelerin listesi, yaln\u0131zca tek bir tarife gerekiyorsa bo\u015f b\u0131rak\u0131n." + }, + "description": "\u00c7e\u015fitli hizmetlerin (\u00f6rn. enerji, gaz, su, \u0131s\u0131tma) t\u00fcketimini, yap\u0131land\u0131r\u0131lm\u0131\u015f bir s\u00fcre boyunca, genellikle ayl\u0131k olarak izleyen bir sens\u00f6r olu\u015fturun. \u015eebeke sayac\u0131 sens\u00f6r\u00fc iste\u011fe ba\u011fl\u0131 olarak t\u00fcketimin tarifelere g\u00f6re b\u00f6l\u00fcnmesini destekler, bu durumda her tarife i\u00e7in bir sens\u00f6r ve mevcut tarifeyi se\u00e7mek i\u00e7in se\u00e7ilen bir varl\u0131k olu\u015fturulur.", + "title": "Kullan\u0131m Sayac\u0131 Ekle" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "Giri\u015f sens\u00f6r\u00fc" + } + } + } + }, + "title": "Yard\u0131mc\u0131 Saya\u00e7" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/zh-Hans.json b/homeassistant/components/utility_meter/translations/zh-Hans.json new file mode 100644 index 00000000000..a1db639adfb --- /dev/null +++ b/homeassistant/components/utility_meter/translations/zh-Hans.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "\u8ba1\u91cf\u590d\u4f4d\u5468\u671f", + "delta_values": "\u8f93\u5165\u4e3a\u5dee\u503c", + "name": "\u540d\u79f0", + "net_consumption": "\u6570\u503c\u53ef\u56de\u9000", + "offset": "\u8ba1\u91cf\u590d\u4f4d\u504f\u79fb", + "source": "\u8f93\u5165\u4f20\u611f\u5668", + "tariffs": "\u652f\u6301\u7684\u8d39\u7387" + }, + "data_description": { + "delta_values": "\u5982\u679c\u6e90\u6570\u636e\u662f\u81ea\u4e0a\u6b21\u8bfb\u6570\u4ee5\u6765\u7684\u5dee\u503c\uff0c\u8bf7\u542f\u7528\u6b64\u9009\u9879\u3002", + "net_consumption": "\u5982\u679c\u6e90\u6570\u636e\u4e0d\u662f\u6301\u7eed\u589e\u957f\uff0c\u800c\u662f\u53ef\u4ee5\u964d\u4f4e\uff0c\u8bf7\u542f\u7528\u6b64\u9009\u9879\u3002", + "offset": "\u6309\u6708\u8ba1\u91cf\u65f6\u590d\u4f4d\u63a8\u8fdf\u7684\u5929\u6570\u3002", + "tariffs": "\u4ee5\u5217\u8868\u7684\u5f62\u5f0f\u5217\u51fa\u6240\u6709\u53ef\u80fd\u7684\u8d39\u7387\u3002\u5982\u679c\u8d39\u7387\u552f\u4e00\uff0c\u8bf7\u7559\u7a7a\u3002" + }, + "description": "\u521b\u5efa\u4f20\u611f\u5668\u6765\u5468\u671f\u6027\u5730\u76d1\u6d4b\u516c\u5171\u80fd\u6e90\uff08\u4f8b\u5982\u6c34\u3001\u7535\u3001\u71c3\u6c14\uff09\u7684\u6d88\u8017\u60c5\u51b5\u3002\u6b64\u7c7b\u4f20\u611f\u5668\u8fd8\u652f\u6301\u591a\u8d39\u7387\u5206\u522b\u7edf\u8ba1\uff08\u4f8b\u5982\u9636\u68af\u6c34\u4ef7\u3001\u5cf0\u8c37\u7535\u4ef7\uff09\uff0c\u4e3a\u6bcf\u79cd\u8d39\u7387\u5206\u522b\u521b\u5efa\u4e00\u4e2a\u4f20\u611f\u5668\uff0c\u5e76\u63d0\u4f9b\u4e00\u4e2a\u9009\u62e9\u5668\u5b9e\u4f53\u6765\u9009\u62e9\u5f53\u524d\u6267\u884c\u7684\u8d39\u7387\u3002", + "title": "\u6dfb\u52a0\u4eea\u8868\u7edf\u8ba1" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "\u8f93\u5165\u4f20\u611f\u5668" + } + } + } + }, + "title": "\u4eea\u8868\u7edf\u8ba1" +} \ No newline at end of file diff --git a/homeassistant/components/utility_meter/translations/zh-Hant.json b/homeassistant/components/utility_meter/translations/zh-Hant.json new file mode 100644 index 00000000000..dc23f73a89e --- /dev/null +++ b/homeassistant/components/utility_meter/translations/zh-Hant.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "user": { + "data": { + "cycle": "\u529f\u8017\u8868\u91cd\u7f6e\u9031\u671f", + "delta_values": "\u589e\u91cf\u503c", + "name": "\u540d\u7a31", + "net_consumption": "\u6de8\u8017\u80fd", + "offset": "\u529f\u8017\u8868\u91cd\u7f6e\u504f\u79fb", + "source": "\u8f38\u5165\u611f\u6e2c\u5668", + "tariffs": "\u652f\u63f4\u5206\u5340\u9069\u7528\u8cbb\u7387" + }, + "data_description": { + "delta_values": "\u5047\u5982\u4f86\u6e90\u70ba\u4e0a\u6b21\u8b80\u53d6\u589e\u91cf\u503c\u3001\u800c\u975e\u7d55\u5c0d\u503c\u5247\u958b\u555f\u3002", + "net_consumption": "\u5047\u5982\u4f86\u6e90\u70ba\u6de8\u529f\u8017\u5247\u958b\u555f\u3001\u8868\u793a\u53ef\u540c\u6642\u589e\u52a0\u53ca\u6e1b\u5c11\u3002", + "offset": "\u6bcf\u6708\u529f\u8017\u91cd\u7f6e\u504f\u79fb\u5929\u6578\u3002", + "tariffs": "\u652f\u63f4\u5206\u5340\u9069\u7528\u8cbb\u7387\u5217\u8868\uff0c\u5047\u5982\u9700\u8981\u55ae\u4e00\u532f\u7387\u3001\u5247\u4fdd\u7559\u7a7a\u767d\u3002" + }, + "description": "\u65b0\u589e\u65bc\u8a2d\u5b9a\u9031\u671f\u6642\u9593\u5167\uff08\u901a\u5e38\u70ba\u6bcf\u6708\uff09\u8ddf\u8e2a\u5404\u7a2e\u516c\u7528\u4e8b\u696d\uff08\u4f8b\u5982\uff0c\u96fb\u529b\u3001\u5929\u7136\u6c23\u3001\u6c34\u3001\u4f9b\u6696\uff09\u8017\u80fd\u6578\u503c\u611f\u6e2c\u5668\u3002\u529f\u8017\u8868\u611f\u6e2c\u5668\u540c\u6642\u9078\u9805\u652f\u63f4\u5206\u5340\u9069\u7528\u8cbb\u7387\u8a08\u7b97\uff0c\u5728\u6b64\u60c5\u6cc1\u4e0b\u3001\u6bcf\u500b\u8cbb\u7387\u7686\u6703\u65b0\u589e\u611f\u6e2c\u5668\u3001\u53ca\u9078\u64c7\u5be6\u9ad4\u4ee5\u9078\u64c7\u76ee\u524d\u8cbb\u7387\u3002", + "title": "\u65b0\u589e\u529f\u8017\u8868" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source": "\u8f38\u5165\u611f\u6e2c\u5668" + } + } + } + }, + "title": "\u529f\u8017\u8868" +} \ No newline at end of file diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 3fa577fdcc0..e6365f21dfe 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -9,7 +9,7 @@ import requests from uvcclient import camera as uvc_camera, nvr import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, CameraEntityFeature from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_SSL from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady @@ -115,7 +115,7 @@ class UnifiVideoCamera(Camera): channels = self._caminfo["channels"] for channel in channels: if channel["isRtspEnabled"]: - return SUPPORT_STREAM + return CameraEntityFeature.STREAM return 0 diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index d049de8f35f..74636e82e69 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -1,9 +1,13 @@ """Support for vacuum cleaner robots (botvacs).""" +from __future__ import annotations + +from collections.abc import Mapping from dataclasses import dataclass from datetime import timedelta +from enum import IntEnum from functools import partial import logging -from typing import final +from typing import Any, final import voluptuous as vol @@ -71,6 +75,28 @@ STATES = [STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR] DEFAULT_NAME = "Vacuum cleaner robot" + +class VacuumEntityFeature(IntEnum): + """Supported features of the vacuum entity.""" + + TURN_ON = 1 + TURN_OFF = 2 + PAUSE = 4 + STOP = 8 + RETURN_HOME = 16 + FAN_SPEED = 32 + BATTERY = 64 + STATUS = 128 + SEND_COMMAND = 256 + LOCATE = 512 + CLEAN_SPOT = 1024 + MAP = 2048 + STATE = 4096 + START = 8192 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the VacuumEntityFeature enum instead. SUPPORT_TURN_ON = 1 SUPPORT_TURN_OFF = 2 SUPPORT_PAUSE = 4 @@ -150,100 +176,107 @@ class _BaseVacuum(Entity): Contains common properties and functions for all vacuum devices. """ + _attr_battery_icon: str + _attr_battery_level: int | None = None + _attr_fan_speed: str | None = None + _attr_fan_speed_list: list[str] + _attr_supported_features: int + @property - def supported_features(self): + def supported_features(self) -> int: """Flag vacuum cleaner features that are supported.""" - raise NotImplementedError() + return self._attr_supported_features @property - def battery_level(self): + def battery_level(self) -> int | None: """Return the battery level of the vacuum cleaner.""" - return None + return self._attr_battery_level @property - def battery_icon(self): + def battery_icon(self) -> str: """Return the battery icon for the vacuum cleaner.""" - raise NotImplementedError() + return self._attr_battery_icon @property - def fan_speed(self): + def fan_speed(self) -> str | None: """Return the fan speed of the vacuum cleaner.""" + return self._attr_fan_speed + + @property + def fan_speed_list(self) -> list[str]: + """Get the list of available fan speed steps of the vacuum cleaner.""" + return self._attr_fan_speed_list + + @property + def capability_attributes(self) -> Mapping[str, Any] | None: + """Return capability attributes.""" + if self.supported_features & VacuumEntityFeature.FAN_SPEED: + return {ATTR_FAN_SPEED_LIST: self.fan_speed_list} return None @property - def fan_speed_list(self): - """Get the list of available fan speed steps of the vacuum cleaner.""" - raise NotImplementedError() - - @property - def capability_attributes(self): - """Return capability attributes.""" - if self.supported_features & SUPPORT_FAN_SPEED: - return {ATTR_FAN_SPEED_LIST: self.fan_speed_list} - - @property - def state_attributes(self): + def state_attributes(self) -> dict[str, Any]: """Return the state attributes of the vacuum cleaner.""" - data = {} + data: dict[str, Any] = {} - if self.supported_features & SUPPORT_BATTERY: + if self.supported_features & VacuumEntityFeature.BATTERY: data[ATTR_BATTERY_LEVEL] = self.battery_level data[ATTR_BATTERY_ICON] = self.battery_icon - if self.supported_features & SUPPORT_FAN_SPEED: + if self.supported_features & VacuumEntityFeature.FAN_SPEED: data[ATTR_FAN_SPEED] = self.fan_speed return data - def stop(self, **kwargs): + def stop(self, **kwargs: Any) -> None: """Stop the vacuum cleaner.""" raise NotImplementedError() - async def async_stop(self, **kwargs): + async def async_stop(self, **kwargs: Any) -> None: """Stop the vacuum cleaner. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.stop, **kwargs)) - def return_to_base(self, **kwargs): + def return_to_base(self, **kwargs: Any) -> None: """Set the vacuum cleaner to return to the dock.""" raise NotImplementedError() - async def async_return_to_base(self, **kwargs): + async def async_return_to_base(self, **kwargs: Any) -> None: """Set the vacuum cleaner to return to the dock. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.return_to_base, **kwargs)) - def clean_spot(self, **kwargs): + def clean_spot(self, **kwargs: Any) -> None: """Perform a spot clean-up.""" raise NotImplementedError() - async def async_clean_spot(self, **kwargs): + async def async_clean_spot(self, **kwargs: Any) -> None: """Perform a spot clean-up. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.clean_spot, **kwargs)) - def locate(self, **kwargs): + def locate(self, **kwargs: Any) -> None: """Locate the vacuum cleaner.""" raise NotImplementedError() - async def async_locate(self, **kwargs): + async def async_locate(self, **kwargs: Any) -> None: """Locate the vacuum cleaner. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.locate, **kwargs)) - def set_fan_speed(self, fan_speed, **kwargs): + def set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set fan speed.""" raise NotImplementedError() - async def async_set_fan_speed(self, fan_speed, **kwargs): + async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set fan speed. This method must be run in the event loop. @@ -252,11 +285,15 @@ class _BaseVacuum(Entity): partial(self.set_fan_speed, fan_speed, **kwargs) ) - def send_command(self, command, params=None, **kwargs): + def send_command( + self, command: str, params: dict | list | None = None, **kwargs: Any + ) -> None: """Send a command to a vacuum cleaner.""" raise NotImplementedError() - async def async_send_command(self, command, params=None, **kwargs): + async def async_send_command( + self, command: str, params: dict | list | None = None, **kwargs: Any + ) -> None: """Send a command to a vacuum cleaner. This method must be run in the event loop. @@ -277,12 +314,12 @@ class VacuumEntity(_BaseVacuum, ToggleEntity): entity_description: VacuumEntityDescription @property - def status(self): + def status(self) -> str | None: """Return the status of the vacuum cleaner.""" return None @property - def battery_icon(self): + def battery_icon(self) -> str: """Return the battery icon for the vacuum cleaner.""" charging = False if self.status is not None: @@ -293,52 +330,52 @@ class VacuumEntity(_BaseVacuum, ToggleEntity): @final @property - def state_attributes(self): + def state_attributes(self) -> dict[str, Any]: """Return the state attributes of the vacuum cleaner.""" data = super().state_attributes - if self.supported_features & SUPPORT_STATUS: + if self.supported_features & VacuumEntityFeature.STATUS: data[ATTR_STATUS] = self.status return data - def turn_on(self, **kwargs): + def turn_on(self, **kwargs: Any) -> None: """Turn the vacuum on and start cleaning.""" raise NotImplementedError() - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the vacuum on and start cleaning. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.turn_on, **kwargs)) - def turn_off(self, **kwargs): + def turn_off(self, **kwargs: Any) -> None: """Turn the vacuum off stopping the cleaning and returning home.""" raise NotImplementedError() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the vacuum off stopping the cleaning and returning home. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.turn_off, **kwargs)) - def start_pause(self, **kwargs): + def start_pause(self, **kwargs: Any) -> None: """Start, pause or resume the cleaning task.""" raise NotImplementedError() - async def async_start_pause(self, **kwargs): + async def async_start_pause(self, **kwargs: Any) -> None: """Start, pause or resume the cleaning task. This method must be run in the event loop. """ await self.hass.async_add_executor_job(partial(self.start_pause, **kwargs)) - async def async_pause(self): + async def async_pause(self) -> None: """Not supported.""" - async def async_start(self): + async def async_start(self) -> None: """Not supported.""" @@ -353,12 +390,12 @@ class StateVacuumEntity(_BaseVacuum): entity_description: StateVacuumEntityDescription @property - def state(self): + def state(self) -> str | None: """Return the state of the vacuum cleaner.""" return None @property - def battery_icon(self): + def battery_icon(self) -> str: """Return the battery icon for the vacuum cleaner.""" charging = bool(self.state == STATE_DOCKED) @@ -366,33 +403,33 @@ class StateVacuumEntity(_BaseVacuum): battery_level=self.battery_level, charging=charging ) - def start(self): + def start(self) -> None: """Start or resume the cleaning task.""" raise NotImplementedError() - async def async_start(self): + async def async_start(self) -> None: """Start or resume the cleaning task. This method must be run in the event loop. """ await self.hass.async_add_executor_job(self.start) - def pause(self): + def pause(self) -> None: """Pause the cleaning task.""" raise NotImplementedError() - async def async_pause(self): + async def async_pause(self) -> None: """Pause the cleaning task. This method must be run in the event loop. """ await self.hass.async_add_executor_job(self.pause) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Not supported.""" - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Not supported.""" - async def async_toggle(self, **kwargs): + async def async_toggle(self, **kwargs: Any) -> None: """Not supported.""" diff --git a/homeassistant/components/vallox/fan.py b/homeassistant/components/vallox/fan.py index 52535be3a29..85b3f501c85 100644 --- a/homeassistant/components/vallox/fan.py +++ b/homeassistant/components/vallox/fan.py @@ -9,8 +9,8 @@ from vallox_websocket_api import Vallox from vallox_websocket_api.exceptions import ValloxApiException from homeassistant.components.fan import ( - SUPPORT_PRESET_MODE, FanEntity, + FanEntityFeature, NotValidPresetModeError, ) from homeassistant.config_entries import ConfigEntry @@ -83,6 +83,8 @@ async def async_setup_entry( class ValloxFan(CoordinatorEntity[ValloxDataUpdateCoordinator], FanEntity): """Representation of the fan.""" + _attr_supported_features = FanEntityFeature.PRESET_MODE + def __init__( self, name: str, @@ -98,11 +100,6 @@ class ValloxFan(CoordinatorEntity[ValloxDataUpdateCoordinator], FanEntity): self._attr_unique_id = str(self.coordinator.data.get_uuid()) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_PRESET_MODE - @property def preset_modes(self) -> list[str]: """Return a list of available preset modes.""" diff --git a/homeassistant/components/vallox/translations/bg.json b/homeassistant/components/vallox/translations/bg.json index 1c2e0aee24c..ec27ad32d0b 100644 --- a/homeassistant/components/vallox/translations/bg.json +++ b/homeassistant/components/vallox/translations/bg.json @@ -14,10 +14,8 @@ "step": { "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", - "name": "\u0418\u043c\u0435" - }, - "title": "Vallox" + "host": "\u0425\u043e\u0441\u0442" + } } } } diff --git a/homeassistant/components/vallox/translations/ca.json b/homeassistant/components/vallox/translations/ca.json index 7a92c24a558..ba8d6287ed4 100644 --- a/homeassistant/components/vallox/translations/ca.json +++ b/homeassistant/components/vallox/translations/ca.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Amfitri\u00f3", - "name": "Nom" - }, - "description": "Configura la integraci\u00f3 Vallox. Si tens problemes amb la configuraci\u00f3, v\u00e9s a {integration_docs_url}.", - "title": "Vallox" + "host": "Amfitri\u00f3" + } } } } diff --git a/homeassistant/components/vallox/translations/cs.json b/homeassistant/components/vallox/translations/cs.json index 2a364830dbf..ffef74e8f9c 100644 --- a/homeassistant/components/vallox/translations/cs.json +++ b/homeassistant/components/vallox/translations/cs.json @@ -8,13 +8,6 @@ }, "error": { "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" - }, - "step": { - "user": { - "data": { - "name": "Jm\u00e9no" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/vallox/translations/de.json b/homeassistant/components/vallox/translations/de.json index 661f45bdfcf..711ef828ad3 100644 --- a/homeassistant/components/vallox/translations/de.json +++ b/homeassistant/components/vallox/translations/de.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Name" - }, - "description": "Richte die Vallox-Integration ein. Wenn du Probleme mit der Konfiguration hast, gehe zu {integration_docs_url} .", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/el.json b/homeassistant/components/vallox/translations/el.json index 4b15c926247..cab5c683ae5 100644 --- a/homeassistant/components/vallox/translations/el.json +++ b/homeassistant/components/vallox/translations/el.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2", - "name": "\u038c\u03bd\u03bf\u03bc\u03b1" - }, - "description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 Vallox. \u0395\u03ac\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03bc\u03b5\u03c4\u03b1\u03b2\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {integration_docs_url}.", - "title": "Vallox" + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2" + } } } } diff --git a/homeassistant/components/vallox/translations/en.json b/homeassistant/components/vallox/translations/en.json index efa32604646..3390fd34854 100644 --- a/homeassistant/components/vallox/translations/en.json +++ b/homeassistant/components/vallox/translations/en.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Name" - }, - "description": "Set up the Vallox integration. If you have problems with configuration go to {integration_docs_url}.", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/es.json b/homeassistant/components/vallox/translations/es.json index 3373f38a725..373ae333ec3 100644 --- a/homeassistant/components/vallox/translations/es.json +++ b/homeassistant/components/vallox/translations/es.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Nombre" - }, - "description": "Configure la integraci\u00f3n de Vallox. Si tiene problemas con la configuraci\u00f3n, vaya a {integration_docs_url} .", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/et.json b/homeassistant/components/vallox/translations/et.json index 8f3a1c7cb3e..14f1854aded 100644 --- a/homeassistant/components/vallox/translations/et.json +++ b/homeassistant/components/vallox/translations/et.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Nimi" - }, - "description": "Seadista Valloxi sidumine. Kui seadistamisega on probleeme, mine aadressile {integration_docs_url}.", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/fr.json b/homeassistant/components/vallox/translations/fr.json index c9823756185..c5db6b81e9e 100644 --- a/homeassistant/components/vallox/translations/fr.json +++ b/homeassistant/components/vallox/translations/fr.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "H\u00f4te", - "name": "Nom" - }, - "description": "Mettre en place l'int\u00e9gration Vallox. Si vous rencontrez des probl\u00e8mes de configuration, acc\u00e9dez \u00e0 {integration_docs_url} .", - "title": "Vallox" + "host": "H\u00f4te" + } } } } diff --git a/homeassistant/components/vallox/translations/he.json b/homeassistant/components/vallox/translations/he.json index ad8f603cb5a..067445268a8 100644 --- a/homeassistant/components/vallox/translations/he.json +++ b/homeassistant/components/vallox/translations/he.json @@ -14,8 +14,7 @@ "step": { "user": { "data": { - "host": "\u05de\u05d0\u05e8\u05d7", - "name": "\u05e9\u05dd" + "host": "\u05de\u05d0\u05e8\u05d7" } } } diff --git a/homeassistant/components/vallox/translations/hu.json b/homeassistant/components/vallox/translations/hu.json index 1b3366b5f0e..58404c9ad73 100644 --- a/homeassistant/components/vallox/translations/hu.json +++ b/homeassistant/components/vallox/translations/hu.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "C\u00edm", - "name": "N\u00e9v" - }, - "description": "A Vallox integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sa. Ha probl\u00e9m\u00e1i vannak a konfigur\u00e1ci\u00f3val, l\u00e1togasson el a https://www.home-assistant.io/integrations/vallox oldalra.", - "title": "Vallox" + "host": "C\u00edm" + } } } } diff --git a/homeassistant/components/vallox/translations/id.json b/homeassistant/components/vallox/translations/id.json index 4f8fbbff6e9..90e7c60b49f 100644 --- a/homeassistant/components/vallox/translations/id.json +++ b/homeassistant/components/vallox/translations/id.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Nama" - }, - "description": "Siapkan integrasi Vallox Jika Anda memiliki masalah dengan konfigurasi, buka {integration_docs_url}.", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/it.json b/homeassistant/components/vallox/translations/it.json index 9aa0e929c50..c82991be7e7 100644 --- a/homeassistant/components/vallox/translations/it.json +++ b/homeassistant/components/vallox/translations/it.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Nome" - }, - "description": "Configura l'integrazione Vallox. Se hai problemi con la configurazione vai su {integration_docs_url}.", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/ja.json b/homeassistant/components/vallox/translations/ja.json index bac8c6a7732..a5467460d22 100644 --- a/homeassistant/components/vallox/translations/ja.json +++ b/homeassistant/components/vallox/translations/ja.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "\u30db\u30b9\u30c8", - "name": "\u540d\u524d" - }, - "description": "Vallox\u306e\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002\u8a2d\u5b9a\u306b\u95a2\u3057\u3066\u554f\u984c\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u306f\u3001https://www.home-assistant.io/integrations/vallox \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "title": "Vallox" + "host": "\u30db\u30b9\u30c8" + } } } } diff --git a/homeassistant/components/vallox/translations/lv.json b/homeassistant/components/vallox/translations/lv.json deleted file mode 100644 index cee9047f155..00000000000 --- a/homeassistant/components/vallox/translations/lv.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Vallox" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/vallox/translations/nl.json b/homeassistant/components/vallox/translations/nl.json index 9cc6ea07a93..b8b5b6bdc57 100644 --- a/homeassistant/components/vallox/translations/nl.json +++ b/homeassistant/components/vallox/translations/nl.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Host", - "name": "Naam" - }, - "description": "Stel de Vallox integratie in. Als u problemen heeft met de configuratie ga dan naar {integration_docs_url}", - "title": "Vallox" + "host": "Host" + } } } } diff --git a/homeassistant/components/vallox/translations/no.json b/homeassistant/components/vallox/translations/no.json index df8fd8e4535..4cc8f8321cb 100644 --- a/homeassistant/components/vallox/translations/no.json +++ b/homeassistant/components/vallox/translations/no.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Vert", - "name": "Navn" - }, - "description": "Sett opp Vallox-integrasjonen. Hvis du har problemer med konfigurasjonen, g\u00e5 til {integration_docs_url} .", - "title": "Vallox" + "host": "Vert" + } } } } diff --git a/homeassistant/components/vallox/translations/pl.json b/homeassistant/components/vallox/translations/pl.json index f96481eba82..b608d4f8e0b 100644 --- a/homeassistant/components/vallox/translations/pl.json +++ b/homeassistant/components/vallox/translations/pl.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Nazwa hosta lub adres IP", - "name": "Nazwa" - }, - "description": "Konfiguracja integracji Vallox. Je\u015bli masz problemy z konfiguracj\u0105 przejd\u017a do {integration_docs_url} .", - "title": "Vallox" + "host": "Nazwa hosta lub adres IP" + } } } } diff --git a/homeassistant/components/vallox/translations/pt-BR.json b/homeassistant/components/vallox/translations/pt-BR.json index 729e5257db8..cccafd932c8 100644 --- a/homeassistant/components/vallox/translations/pt-BR.json +++ b/homeassistant/components/vallox/translations/pt-BR.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Nome do host", - "name": "Nome" - }, - "description": "Configure a integra\u00e7\u00e3o Vallox. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, v\u00e1 para {integration_docs_url} .", - "title": "Vallox" + "host": "Nome do host" + } } } } diff --git a/homeassistant/components/vallox/translations/ru.json b/homeassistant/components/vallox/translations/ru.json index 70e9551ae11..a1165b287d1 100644 --- a/homeassistant/components/vallox/translations/ru.json +++ b/homeassistant/components/vallox/translations/ru.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "\u0425\u043e\u0441\u0442", - "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" - }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Vallox. \u0415\u0441\u043b\u0438 \u0443 \u0412\u0430\u0441 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439: {integration_docs_url}.", - "title": "Vallox" + "host": "\u0425\u043e\u0441\u0442" + } } } } diff --git a/homeassistant/components/vallox/translations/tr.json b/homeassistant/components/vallox/translations/tr.json index 1958a0535b6..8d5780d2019 100644 --- a/homeassistant/components/vallox/translations/tr.json +++ b/homeassistant/components/vallox/translations/tr.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "Sunucu", - "name": "Ad" - }, - "description": "Vallox entegrasyonunu ayarlay\u0131n. Yap\u0131land\u0131rmayla ilgili sorunlar\u0131n\u0131z varsa {integration_docs_url} adresine gidin.", - "title": "Vallox" + "host": "Sunucu" + } } } } diff --git a/homeassistant/components/vallox/translations/zh-Hant.json b/homeassistant/components/vallox/translations/zh-Hant.json index c9f1d13fa08..d9e2150dce9 100644 --- a/homeassistant/components/vallox/translations/zh-Hant.json +++ b/homeassistant/components/vallox/translations/zh-Hant.json @@ -14,11 +14,8 @@ "step": { "user": { "data": { - "host": "\u4e3b\u6a5f\u7aef", - "name": "\u540d\u7a31" - }, - "description": "\u8a2d\u5b9a Vallox \u6574\u5408\u3002\u5047\u5982\u9700\u8981\u5354\u52a9\uff0c\u8acb\u53c3\u8003\uff1a{integration_docs_url}\u3002", - "title": "Vallox" + "host": "\u4e3b\u6a5f\u7aef" + } } } } diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 5dce4033074..18e01d3ae87 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -6,11 +6,7 @@ from typing import Any from velbusaio.channels import Temperature as VelbusTemp from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -38,10 +34,12 @@ class VelbusClimate(VelbusEntity, ClimateEntity): """Representation of a Velbus thermostat.""" _channel: VelbusTemp - _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) _attr_temperature_unit = TEMP_CELSIUS - _attr_hvac_mode = HVAC_MODE_HEAT - _attr_hvac_modes = [HVAC_MODE_HEAT] + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] _attr_preset_modes = list(PRESET_MODES) @property diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 4afe02f4637..6bd1629d3a3 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -7,11 +7,8 @@ from velbusaio.channels import Blind as VelbusBlind from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, - SUPPORT_STOP, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -45,10 +42,17 @@ class VelbusCover(VelbusEntity, CoverEntity): super().__init__(channel) if self._channel.support_position(): self._attr_supported_features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION ) else: - self._attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP + self._attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + ) @property def is_closed(self) -> bool | None: diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 2926b30c22f..f562e250892 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -15,10 +15,9 @@ from homeassistant.components.light import ( ATTR_TRANSITION, FLASH_LONG, FLASH_SHORT, - SUPPORT_BRIGHTNESS, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -49,7 +48,9 @@ class VelbusLight(VelbusEntity, LightEntity): """Representation of a Velbus light.""" _channel: VelbusDimmer - _attr_supported_features = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = LightEntityFeature.TRANSITION @property def is_on(self) -> bool: @@ -97,7 +98,9 @@ class VelbusButtonLight(VelbusEntity, LightEntity): _channel: VelbusButton _attr_entity_registry_enabled_default = False _attr_entity_category = EntityCategory.CONFIG - _attr_supported_features = SUPPORT_FLASH + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + _attr_supported_features = LightEntityFeature.FLASH def __init__(self, channel: VelbusChannel) -> None: """Initialize the button light (led).""" diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index ca529d4c572..f4f449509a0 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -7,16 +7,9 @@ from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, - SUPPORT_STOP_TILT, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -46,14 +39,17 @@ class VeluxCover(VeluxEntity, CoverEntity): def supported_features(self): """Flag supported features.""" supported_features = ( - SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + | CoverEntityFeature.STOP ) if self.current_cover_tilt_position is not None: supported_features |= ( - SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_SET_TILT_POSITION - | SUPPORT_STOP_TILT + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION + | CoverEntityFeature.STOP_TILT ) return supported_features diff --git a/homeassistant/components/velux/light.py b/homeassistant/components/velux/light.py index 311a4dba283..e6ca8ea5c2b 100644 --- a/homeassistant/components/velux/light.py +++ b/homeassistant/components/velux/light.py @@ -2,13 +2,8 @@ from __future__ import annotations from pyvlx import Intensity, LighteningDevice -from pyvlx.node import Node -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - COLOR_MODE_BRIGHTNESS, - LightEntity, -) +from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -33,12 +28,8 @@ async def async_setup_platform( class VeluxLight(VeluxEntity, LightEntity): """Representation of a Velux light.""" - def __init__(self, node: Node) -> None: - """Initialize the Velux light.""" - super().__init__(node) - - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_color_mode = ColorMode.BRIGHTNESS @property def brightness(self): diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index 441e46c38b8..efa1c56ccbc 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -8,23 +8,13 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, FAN_AUTO, FAN_ON, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( @@ -54,8 +44,6 @@ from .const import ( DEFAULT_SSL, DOMAIN, HOLD_MODE_TEMPERATURE, - VALID_FAN_STATES, - VALID_THERMOSTAT_MODES, ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -116,6 +104,9 @@ async def async_setup_platform( class VenstarThermostat(VenstarEntity, ClimateEntity): """Representation of a Venstar thermostat.""" + _attr_fan_modes = [FAN_ON, FAN_AUTO] + _attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF, HVACMode.AUTO] + def __init__( self, venstar_data_coordinator: VenstarDataUpdateCoordinator, @@ -124,9 +115,9 @@ class VenstarThermostat(VenstarEntity, ClimateEntity): """Initialize the thermostat.""" super().__init__(venstar_data_coordinator, config) self._mode_map = { - HVAC_MODE_HEAT: self._client.MODE_HEAT, - HVAC_MODE_COOL: self._client.MODE_COOL, - HVAC_MODE_AUTO: self._client.MODE_AUTO, + HVACMode.HEAT: self._client.MODE_HEAT, + HVACMode.COOL: self._client.MODE_COOL, + HVACMode.AUTO: self._client.MODE_AUTO, } self._attr_unique_id = config.entry_id self._attr_name = self._client.name @@ -134,13 +125,17 @@ class VenstarThermostat(VenstarEntity, ClimateEntity): @property def supported_features(self): """Return the list of supported features.""" - features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_PRESET_MODE + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + ) if self._client.mode == self._client.MODE_AUTO: - features |= SUPPORT_TARGET_TEMPERATURE_RANGE + features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE if self._client.hum_setpoint is not None: - features |= SUPPORT_TARGET_HUMIDITY + features |= ClimateEntityFeature.TARGET_HUMIDITY return features @@ -160,16 +155,6 @@ class VenstarThermostat(VenstarEntity, ClimateEntity): return TEMP_FAHRENHEIT return TEMP_CELSIUS - @property - def fan_modes(self): - """Return the list of available fan modes.""" - return VALID_FAN_STATES - - @property - def hvac_modes(self): - """Return the list of available operation modes.""" - return VALID_THERMOSTAT_MODES - @property def current_temperature(self): """Return the current temperature.""" @@ -181,26 +166,26 @@ class VenstarThermostat(VenstarEntity, ClimateEntity): return self._client.get_indoor_humidity() @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation mode ie. heat, cool, auto.""" if self._client.mode == self._client.MODE_HEAT: - return HVAC_MODE_HEAT + return HVACMode.HEAT if self._client.mode == self._client.MODE_COOL: - return HVAC_MODE_COOL + return HVACMode.COOL if self._client.mode == self._client.MODE_AUTO: - return HVAC_MODE_AUTO - return HVAC_MODE_OFF + return HVACMode.AUTO + return HVACMode.OFF @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return current operation mode ie. heat, cool, auto.""" if self._client.state == self._client.STATE_IDLE: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if self._client.state == self._client.STATE_HEATING: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if self._client.state == self._client.STATE_COOLING: - return CURRENT_HVAC_COOL - return CURRENT_HVAC_OFF + return HVACAction.COOLING + return HVACAction.OFF @property def fan_mode(self): @@ -269,13 +254,13 @@ class VenstarThermostat(VenstarEntity, ClimateEntity): """Return valid preset modes.""" return [PRESET_NONE, PRESET_AWAY, HOLD_MODE_TEMPERATURE] - def _set_operation_mode(self, operation_mode): + def _set_operation_mode(self, operation_mode: HVACMode): """Change the operation mode (internal).""" - if operation_mode == HVAC_MODE_HEAT: + if operation_mode == HVACMode.HEAT: success = self._client.set_mode(self._client.MODE_HEAT) - elif operation_mode == HVAC_MODE_COOL: + elif operation_mode == HVACMode.COOL: success = self._client.set_mode(self._client.MODE_COOL) - elif operation_mode == HVAC_MODE_AUTO: + elif operation_mode == HVACMode.AUTO: success = self._client.set_mode(self._client.MODE_AUTO) else: success = self._client.set_mode(self._client.MODE_OFF) @@ -334,7 +319,7 @@ class VenstarThermostat(VenstarEntity, ClimateEntity): _LOGGER.error("Failed to change the fan mode") self.schedule_update_ha_state() - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" self._set_operation_mode(hvac_mode) self.schedule_update_ha_state() diff --git a/homeassistant/components/venstar/const.py b/homeassistant/components/venstar/const.py index ec672afd714..31eb5724558 100644 --- a/homeassistant/components/venstar/const.py +++ b/homeassistant/components/venstar/const.py @@ -1,14 +1,6 @@ """The venstar component.""" import logging -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, -) -from homeassistant.const import STATE_ON - DOMAIN = "venstar" ATTR_FAN_STATE = "fan_state" @@ -18,9 +10,6 @@ CONF_HUMIDIFIER = "humidifier" DEFAULT_SSL = False -VALID_FAN_STATES = [STATE_ON, HVAC_MODE_AUTO] -VALID_THERMOSTAT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_OFF, HVAC_MODE_AUTO] - HOLD_MODE_OFF = "off" HOLD_MODE_TEMPERATURE = "temperature" diff --git a/homeassistant/components/venstar/translations/pl.json b/homeassistant/components/venstar/translations/pl.json index ad0e76435dc..fdaf30ead9f 100644 --- a/homeassistant/components/venstar/translations/pl.json +++ b/homeassistant/components/venstar/translations/pl.json @@ -16,7 +16,7 @@ "ssl": "Certyfikat SSL", "username": "Nazwa u\u017cytkownika" }, - "title": "Po\u0142\u0105cz z termostatem Venstar" + "title": "Po\u0142\u0105czenie z termostatem Venstar" } } } diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 399d4ace278..b182e7635ab 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -9,12 +9,8 @@ from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity from homeassistant.components.climate.const import ( FAN_AUTO, FAN_ON, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -31,8 +27,7 @@ from .common import ControllerData, get_controller_data FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO] -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE -SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF] +SUPPORT_HVAC = [HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL, HVACMode.OFF] async def async_setup_entry( @@ -54,6 +49,11 @@ async def async_setup_entry( class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): """Representation of a Vera Thermostat.""" + _attr_hvac_modes = SUPPORT_HVAC + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) + def __init__( self, vera_device: veraApi.VeraThermostat, controller_data: ControllerData ) -> None: @@ -62,32 +62,19 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_FLAGS - - @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. """ mode = self.vera_device.get_hvac_mode() if mode == "HeatOn": - return HVAC_MODE_HEAT + return HVACMode.HEAT if mode == "CoolOn": - return HVAC_MODE_COOL + return HVACMode.COOL if mode == "AutoChangeOver": - return HVAC_MODE_HEAT_COOL - return HVAC_MODE_OFF - - @property - def hvac_modes(self) -> list[str]: - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ - return SUPPORT_HVAC + return HVACMode.HEAT_COOL + return HVACMode.OFF @property def fan_mode(self) -> str | None: @@ -142,15 +129,15 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): self.schedule_update_ha_state() - def set_hvac_mode(self, hvac_mode) -> None: + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: self.vera_device.turn_off() - elif hvac_mode == HVAC_MODE_HEAT_COOL: + elif hvac_mode == HVACMode.HEAT_COOL: self.vera_device.turn_auto_on() - elif hvac_mode == HVAC_MODE_COOL: + elif hvac_mode == HVACMode.COOL: self.vera_device.turn_cool_on() - elif hvac_mode == HVAC_MODE_HEAT: + elif hvac_mode == HVACMode.HEAT: self.vera_device.turn_heat_on() self.schedule_update_ha_state() diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index f306d09bf77..8f541bf21b4 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -6,11 +6,16 @@ from typing import cast import pyvera as veraApi -from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity +from homeassistant.components.sensor import ( + ENTITY_ID_FORMAT, + SensorDeviceClass, + SensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( LIGHT_LUX, PERCENTAGE, + POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT, Platform, @@ -59,6 +64,19 @@ class VeraSensor(VeraDevice[veraApi.VeraSensor], SensorEntity): """Return the name of the sensor.""" return self.current_value + @property + def device_class(self) -> str | None: + """Return the class of this entity.""" + if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: + return SensorDeviceClass.TEMPERATURE + if self.vera_device.category == veraApi.CATEGORY_LIGHT_SENSOR: + return SensorDeviceClass.ILLUMINANCE + if self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR: + return SensorDeviceClass.HUMIDITY + if self.vera_device.category == veraApi.CATEGORY_POWER_METER: + return SensorDeviceClass.POWER + return None + @property def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" @@ -72,7 +90,7 @@ class VeraSensor(VeraDevice[veraApi.VeraSensor], SensorEntity): if self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR: return PERCENTAGE if self.vera_device.category == veraApi.CATEGORY_POWER_METER: - return "watts" + return POWER_WATT return None def update(self) -> None: diff --git a/homeassistant/components/vera/translations/ca.json b/homeassistant/components/vera/translations/ca.json index 13a889cb7db..6aa7cd8292d 100644 --- a/homeassistant/components/vera/translations/ca.json +++ b/homeassistant/components/vera/translations/ca.json @@ -10,8 +10,9 @@ "lights": "Identificadors de dispositiu dels commutadors Vera a tractar com a llums a Home Assistant.", "vera_controller_url": "URL del controlador" }, - "description": "Proporciona un URL pel controlador Vera. Hauria de ser similar al seg\u00fcent: http://192.168.1.161:3480.", - "title": "Configuraci\u00f3 del controlador Vera" + "data_description": { + "vera_controller_url": "Hauria de tenir aquest aspecte: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/cs.json b/homeassistant/components/vera/translations/cs.json index df93575a945..33b7cbf6625 100644 --- a/homeassistant/components/vera/translations/cs.json +++ b/homeassistant/components/vera/translations/cs.json @@ -9,9 +9,7 @@ "exclude": "ID za\u0159\u00edzen\u00ed Vera, kter\u00e1 chcete vylou\u010dit z Home Assistant.", "lights": "ID za\u0159\u00edzen\u00ed Vera, se kter\u00fdmi m\u00e1 Home Assistant zach\u00e1zet jako se sv\u011btly.", "vera_controller_url": "URL adresa ovlada\u010de" - }, - "description": "N\u00ed\u017ee zadejte adresu URL odvlada\u010de Vera. M\u011blo by to vypadat takto: http://192.168.1.161:3480.", - "title": "Nastaven\u00ed ovlada\u010de Vera" + } } } }, diff --git a/homeassistant/components/vera/translations/de.json b/homeassistant/components/vera/translations/de.json index 999dfc8213f..f13e347ffd9 100644 --- a/homeassistant/components/vera/translations/de.json +++ b/homeassistant/components/vera/translations/de.json @@ -10,8 +10,9 @@ "lights": "Vera Switch-Ger\u00e4te-IDs, die im Home Assistant als Lichter behandelt werden sollen.", "vera_controller_url": "Controller-URL" }, - "description": "Stelle unten eine Vera-Controller-URL zur Verf\u00fcgung. Sie sollte wie folgt aussehen: http://192.168.1.161:3480.", - "title": "Richte den Vera-Controller ein" + "data_description": { + "vera_controller_url": "Sie sollte wie folgt aussehen: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/el.json b/homeassistant/components/vera/translations/el.json index 1039fb01ccd..bb667455132 100644 --- a/homeassistant/components/vera/translations/el.json +++ b/homeassistant/components/vera/translations/el.json @@ -10,8 +10,9 @@ "lights": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 \u03bc\u03b5\u03c4\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 Vera \u03b3\u03b9\u03b1 \u03c7\u03c1\u03ae\u03c3\u03b7 \u03c9\u03c2 \u03c6\u03ce\u03c4\u03b1 \u03c3\u03c4\u03bf Home Assistant.", "vera_controller_url": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae" }, - "description": "\u0394\u03ce\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03c4\u03bf\u03c5 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9. \u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bc\u03bf\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc: http://192.168.1.161:3480.", - "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ae Vera" + "data_description": { + "vera_controller_url": "\u0398\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bc\u03bf\u03b9\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03cc: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/en.json b/homeassistant/components/vera/translations/en.json index 94f490d71ee..53c60e39c01 100644 --- a/homeassistant/components/vera/translations/en.json +++ b/homeassistant/components/vera/translations/en.json @@ -10,8 +10,9 @@ "lights": "Vera switch device ids to treat as lights in Home Assistant.", "vera_controller_url": "Controller URL" }, - "description": "Provide a Vera controller URL below. It should look like this: http://192.168.1.161:3480.", - "title": "Setup Vera controller" + "data_description": { + "vera_controller_url": "It should look like this: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/es-419.json b/homeassistant/components/vera/translations/es-419.json index 834c74b7ed3..40b78726144 100644 --- a/homeassistant/components/vera/translations/es-419.json +++ b/homeassistant/components/vera/translations/es-419.json @@ -9,9 +9,7 @@ "exclude": "Identificadores de dispositivo de Vera para excluir de Home Assistant.", "lights": "Vera cambia los identificadores del dispositivo para tratarlos como luces en Home Assistant.", "vera_controller_url": "URL del controlador" - }, - "description": "Proporcione una URL del controlador Vera a continuaci\u00f3n. Deber\u00eda verse as\u00ed: http://192.168.1.161:3480.", - "title": "Configurar el controlador Vera" + } } } }, diff --git a/homeassistant/components/vera/translations/es.json b/homeassistant/components/vera/translations/es.json index d34e106e9eb..0cccacaa88f 100644 --- a/homeassistant/components/vera/translations/es.json +++ b/homeassistant/components/vera/translations/es.json @@ -9,9 +9,7 @@ "exclude": "Identificadores de dispositivos Vera a excluir de Home Assistant", "lights": "Identificadores de interruptores Vera que deben ser tratados como luces en Home Assistant", "vera_controller_url": "URL del controlador" - }, - "description": "Introduce una URL para el controlador Vera a continuaci\u00f3n. Ser\u00eda algo como: http://192.168.1.161:3480.", - "title": "Configurar el controlador Vera" + } } } }, diff --git a/homeassistant/components/vera/translations/et.json b/homeassistant/components/vera/translations/et.json index 4afb098caa6..36d2cae71bf 100644 --- a/homeassistant/components/vera/translations/et.json +++ b/homeassistant/components/vera/translations/et.json @@ -10,8 +10,9 @@ "lights": "Vera l\u00fclitite ID'd mida neid k\u00e4sitleda Home Assistantis tuledena.", "vera_controller_url": "Kontrolleri URL" }, - "description": "Esita allpool Vera kontrolleri URL. See peaks v\u00e4lja n\u00e4gema selline: http://192.168.1.161:3480.", - "title": "Seadista Vera kontroller" + "data_description": { + "vera_controller_url": "See peaks v\u00e4lja n\u00e4gema selline: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/fr.json b/homeassistant/components/vera/translations/fr.json index 133319c552d..f57145a9a24 100644 --- a/homeassistant/components/vera/translations/fr.json +++ b/homeassistant/components/vera/translations/fr.json @@ -10,8 +10,9 @@ "lights": "Identifiants des interrupteurs vera \u00e0 traiter comme des lumi\u00e8res dans Home Assistant", "vera_controller_url": "URL du contr\u00f4leur" }, - "description": "Fournissez une URL de contr\u00f4leur Vera ci-dessous. Cela devrait ressembler \u00e0 ceci : http://192.168.1.161:3480.", - "title": "Configurer le contr\u00f4leur Vera" + "data_description": { + "vera_controller_url": "Cela devrait ressembler \u00e0 ceci\u00a0: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/hu.json b/homeassistant/components/vera/translations/hu.json index d1d4910c97a..30cf7e5e7d1 100644 --- a/homeassistant/components/vera/translations/hu.json +++ b/homeassistant/components/vera/translations/hu.json @@ -10,8 +10,9 @@ "lights": "A Vera kapcsol\u00f3eszk\u00f6z-azonos\u00edt\u00f3k f\u00e9nyk\u00e9nt kezelhet\u0151k a Home Assistant alkalmaz\u00e1sban.", "vera_controller_url": "Vez\u00e9rl\u0151 URL" }, - "description": "Adja meg a Vera vez\u00e9rl\u0151 URL-j\u00e9t al\u00e1bb. Hasonl\u00f3k\u00e9ppen kell kin\u00e9znie: http://192.168.1.161:3480.", - "title": "Vera vez\u00e9rl\u0151 be\u00e1ll\u00edt\u00e1sa" + "data_description": { + "vera_controller_url": "\u00cdgy kell kin\u00e9znie: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/id.json b/homeassistant/components/vera/translations/id.json index 435fc722dba..99422f10be6 100644 --- a/homeassistant/components/vera/translations/id.json +++ b/homeassistant/components/vera/translations/id.json @@ -10,8 +10,9 @@ "lights": "ID perangkat sakelar Vera yang diperlakukan sebagai lampu di Home Assistant", "vera_controller_url": "URL Pengontrol" }, - "description": "Tentukan URL pengontrol Vera di bawah. Ini akan terlihat seperti ini: http://192.168.1.161:3480.", - "title": "Siapkan pengontrol Vera" + "data_description": { + "vera_controller_url": "Seharusnya terlihat seperti ini: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/it.json b/homeassistant/components/vera/translations/it.json index a3832914ac6..292e4048f0a 100644 --- a/homeassistant/components/vera/translations/it.json +++ b/homeassistant/components/vera/translations/it.json @@ -10,8 +10,9 @@ "lights": "Gli ID dei dispositivi switch Vera da trattare come luci in Home Assistant.", "vera_controller_url": "URL del controller" }, - "description": "Fornisci un URL controller Vera di seguito. Dovrebbe assomigliare a questo: http://192.168.1.161:3480.", - "title": "Configurazione controller Vera" + "data_description": { + "vera_controller_url": "Dovrebbe apparire cos\u00ec: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/ja.json b/homeassistant/components/vera/translations/ja.json index 2f800ee7b83..83ca62f4abf 100644 --- a/homeassistant/components/vera/translations/ja.json +++ b/homeassistant/components/vera/translations/ja.json @@ -10,8 +10,9 @@ "lights": "Vera\u306f\u3001Home Assistant\u3067\u30e9\u30a4\u30c8\u3068\u3057\u3066\u6271\u3046\u30c7\u30d0\u30a4\u30b9ID\u3092\u5207\u308a\u66ff\u3048\u307e\u3059\u3002", "vera_controller_url": "\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u306eURL" }, - "description": "Vera\u306e\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u306eURL\u3092\u4ee5\u4e0b\u306b\u793a\u3057\u307e\u3059: http://192.168.1.161:3480 \u306e\u3088\u3046\u306b\u306a\u3063\u3066\u3044\u308b\u306f\u305a\u3067\u3059\u3002", - "title": "Vera controller\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7" + "data_description": { + "vera_controller_url": "\u6b21\u306e\u3088\u3046\u306b\u306a\u308a\u307e\u3059: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/ko.json b/homeassistant/components/vera/translations/ko.json index 0990435cb4a..430af1be385 100644 --- a/homeassistant/components/vera/translations/ko.json +++ b/homeassistant/components/vera/translations/ko.json @@ -9,9 +9,7 @@ "exclude": "Home Assistant\uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant\uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4.", "vera_controller_url": "\ucee8\ud2b8\ub864\ub7ec URL" - }, - "description": "\uc544\ub798\uc5d0 Vera \ucee8\ud2b8\ub864\ub7ec URL \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. http://192.168.1.161:3480 \uacfc \uac19\uc740 \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.", - "title": "Vera \ucee8\ud2b8\ub864\ub7ec \uc124\uc815\ud558\uae30" + } } } }, diff --git a/homeassistant/components/vera/translations/lb.json b/homeassistant/components/vera/translations/lb.json index 0f3c8acc028..307218b2b23 100644 --- a/homeassistant/components/vera/translations/lb.json +++ b/homeassistant/components/vera/translations/lb.json @@ -9,9 +9,7 @@ "exclude": "IDs vu Vera Apparater d\u00e9i vun Home Assistant ausgeschloss solle ginn.", "lights": "IDs vun Apparater vu Vera Schalter d\u00e9i als Luuchten am Home Assistant trait\u00e9iert ginn.", "vera_controller_url": "Kontroller URL" - }, - "description": "Vera Kontroller URL uginn: D\u00e9i sollt sou ausgesinn:\nhttp://192.168.1.161:3480.", - "title": "Vera Kontroller ariichten" + } } } }, diff --git a/homeassistant/components/vera/translations/nl.json b/homeassistant/components/vera/translations/nl.json index b4aa4a1d7a3..bc81fa3563e 100644 --- a/homeassistant/components/vera/translations/nl.json +++ b/homeassistant/components/vera/translations/nl.json @@ -10,8 +10,9 @@ "lights": "Vera-schakelapparaat id's behandelen als lichten in Home Assistant.", "vera_controller_url": "Controller-URL" }, - "description": "Geef hieronder een URL voor de Vera-controller op. Het zou er zo uit moeten zien: http://192.168.1.161:3480.", - "title": "Stel Vera controller in" + "data_description": { + "vera_controller_url": "Het zou er zo uit moeten zien: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/no.json b/homeassistant/components/vera/translations/no.json index f1454be6799..807b44dc84e 100644 --- a/homeassistant/components/vera/translations/no.json +++ b/homeassistant/components/vera/translations/no.json @@ -10,8 +10,9 @@ "lights": "Vera bytter enhets ID-er for \u00e5 behandle som lys i Home Assistant", "vera_controller_url": "URL-adresse for kontroller" }, - "description": "Gi en Vera-kontroller-URL nedenfor. Det skal se slik ut: http://192.168.1.161:3480.", - "title": "Oppsett Vera-kontroller" + "data_description": { + "vera_controller_url": "Det skal se slik ut: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/pl.json b/homeassistant/components/vera/translations/pl.json index 5b7b6886085..5be37d2a571 100644 --- a/homeassistant/components/vera/translations/pl.json +++ b/homeassistant/components/vera/translations/pl.json @@ -10,8 +10,9 @@ "lights": "Identyfikatory prze\u0142\u0105cznik\u00f3w Vera, kt\u00f3re maj\u0105 by\u0107 traktowane jako \u015bwiat\u0142a w Home Assistant.", "vera_controller_url": "Adres URL kontrolera" }, - "description": "Podaj adres URL kontrolera Vera. Powinien on wygl\u0105da\u0107 tak: http://192.168.1.161:3480.", - "title": "Konfiguracja kontrolera Vera" + "data_description": { + "vera_controller_url": "Powinno to wygl\u0105da\u0107 tak: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/pt-BR.json b/homeassistant/components/vera/translations/pt-BR.json index 361b0487617..b3c39d4cbd7 100644 --- a/homeassistant/components/vera/translations/pt-BR.json +++ b/homeassistant/components/vera/translations/pt-BR.json @@ -10,8 +10,9 @@ "lights": "Vera - alternar os IDs do dispositivo para tratar como luzes no Home Assistant.", "vera_controller_url": "URL do controlador" }, - "description": "Forne\u00e7a um URL do controlador Vera abaixo. Deve ficar assim: http://192.168.1.161:3480.", - "title": "Configurar controlador Vera" + "data_description": { + "vera_controller_url": "Deve ficar assim: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/ru.json b/homeassistant/components/vera/translations/ru.json index 857ffaa5bf5..43dc0cfd168 100644 --- a/homeassistant/components/vera/translations/ru.json +++ b/homeassistant/components/vera/translations/ru.json @@ -10,8 +10,9 @@ "lights": "ID \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u0435\u0439 Vera, \u0434\u043b\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u0432 \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u0435", "vera_controller_url": "URL-\u0430\u0434\u0440\u0435\u0441 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430" }, - "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 URL-\u0430\u0434\u0440\u0435\u0441 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 'http://192.168.1.161:3480').", - "title": "Vera" + "data_description": { + "vera_controller_url": "\u0414\u043e\u043b\u0436\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/sl.json b/homeassistant/components/vera/translations/sl.json index 355c6353388..01b2f755ac5 100644 --- a/homeassistant/components/vera/translations/sl.json +++ b/homeassistant/components/vera/translations/sl.json @@ -9,9 +9,7 @@ "exclude": "ID-ji naprav Vera, ki jih \u017eelite izklju\u010diti iz programa Home Assistant.", "lights": "ID-ji stikal Vera, ki naj jih Home Assistant tretira kot lu\u010di.", "vera_controller_url": "URL krmilnika" - }, - "description": "Spodaj navedite URL krmilnika Vera. Izgledati bi moral takole: http://192.168.1.161:3480.", - "title": "Nastavite krmilnik Vera" + } } } }, diff --git a/homeassistant/components/vera/translations/tr.json b/homeassistant/components/vera/translations/tr.json index fa66c1c9806..465aed0cb90 100644 --- a/homeassistant/components/vera/translations/tr.json +++ b/homeassistant/components/vera/translations/tr.json @@ -10,8 +10,9 @@ "lights": "Vera, Home Assistant'ta \u0131\u015f\u0131k gibi davranmak i\u00e7in cihaz kimliklerini de\u011fi\u015ftirir.", "vera_controller_url": "Denetleyici URL'si" }, - "description": "A\u015fa\u011f\u0131da bir Vera denetleyici URL'si sa\u011flay\u0131n. \u015eu \u015fekilde g\u00f6r\u00fcnmelidir: http://192.168.1.161:3480.", - "title": "Vera denetleyicisini kurun" + "data_description": { + "vera_controller_url": "\u015eu \u015fekilde g\u00f6r\u00fcnmelidir: http://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/vera/translations/uk.json b/homeassistant/components/vera/translations/uk.json index 8c591a1cc10..5042d04c26a 100644 --- a/homeassistant/components/vera/translations/uk.json +++ b/homeassistant/components/vera/translations/uk.json @@ -9,9 +9,7 @@ "exclude": "ID \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera, \u0434\u043b\u044f \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0437 Home Assistant", "lights": "ID \u0432\u0438\u043c\u0438\u043a\u0430\u0447\u0456\u0432 Vera, \u0434\u043b\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443 \u0432 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f", "vera_controller_url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430" - }, - "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.1.161:3480').", - "title": "Vera" + } } } }, diff --git a/homeassistant/components/vera/translations/zh-Hant.json b/homeassistant/components/vera/translations/zh-Hant.json index b8d7031ee12..802ce7c97f1 100644 --- a/homeassistant/components/vera/translations/zh-Hant.json +++ b/homeassistant/components/vera/translations/zh-Hant.json @@ -10,8 +10,9 @@ "lights": "\u65bc Home Assistant \u4e2d\u8996\u70ba\u71c8\u5149\u7684 Vera \u958b\u95dc\u88dd\u7f6e ID\u3002", "vera_controller_url": "\u63a7\u5236\u5668 URL" }, - "description": "\u65bc\u4e0b\u65b9\u63d0\u4f9b Vera \u63a7\u5236\u5668 URL\u3002\u683c\u5f0f\u61c9\u8a72\u70ba\uff1ahttp://192.168.1.161:3480\u3002", - "title": "\u8a2d\u5b9a Vera \u63a7\u5236\u5668" + "data_description": { + "vera_controller_url": "\u770b\u8d77\u4f86\u61c9\u8a72\u985e\u4f3c\uff1ahttp://192.168.1.161:3480" + } } } }, diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 4cc5e8f6cb3..8f92ee0a5dd 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -4,12 +4,9 @@ from __future__ import annotations import asyncio from homeassistant.components.alarm_control_panel import ( - FORMAT_NUMBER, AlarmControlPanelEntity, -) -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -35,9 +32,12 @@ class VerisureAlarm( ): """Representation of a Verisure alarm status.""" - _attr_code_format = FORMAT_NUMBER + _attr_code_format = CodeFormat.NUMBER _attr_name = "Verisure Alarm" - _attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) @property def device_info(self) -> DeviceInfo: diff --git a/homeassistant/components/verisure/translations/hu.json b/homeassistant/components/verisure/translations/hu.json index 91c0292c3b8..324033b666a 100644 --- a/homeassistant/components/verisure/translations/hu.json +++ b/homeassistant/components/verisure/translations/hu.json @@ -17,14 +17,14 @@ }, "reauth_confirm": { "data": { - "description": "Hiteles\u00edts \u00fajra a Verisure My Pages fi\u00f3koddal.", + "description": "Hiteles\u00edtsen \u00fajra a Verisure My Pages fi\u00f3kj\u00e1val.", "email": "E-mail", "password": "Jelsz\u00f3" } }, "user": { "data": { - "description": "Jelentkezz be a Verisure My Pages fi\u00f3koddal.", + "description": "Jelentkezzen be a Verisure My Pages fi\u00f3kj\u00e1val.", "email": "E-mail", "password": "Jelsz\u00f3" } diff --git a/homeassistant/components/version/manifest.json b/homeassistant/components/version/manifest.json index 80b3f2fb3dc..cd513b16e33 100644 --- a/homeassistant/components/version/manifest.json +++ b/homeassistant/components/version/manifest.json @@ -3,7 +3,7 @@ "name": "Version", "documentation": "https://www.home-assistant.io/integrations/version", "requirements": ["pyhaversion==22.4.1"], - "codeowners": ["@fabaff", "@ludeeus"], + "codeowners": ["@ludeeus"], "quality_scale": "internal", "iot_class": "local_push", "config_flow": true, diff --git a/homeassistant/components/version/translations/he.json b/homeassistant/components/version/translations/he.json index cdb921611c4..c5508040834 100644 --- a/homeassistant/components/version/translations/he.json +++ b/homeassistant/components/version/translations/he.json @@ -2,6 +2,17 @@ "config": { "abort": { "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" + }, + "step": { + "user": { + "title": "\u05d1\u05d7\u05d9\u05e8\u05ea \u05e1\u05d5\u05d2 \u05d4\u05ea\u05e7\u05e0\u05d4" + }, + "version_source": { + "data": { + "beta": "\u05dc\u05db\u05dc\u05d5\u05dc \u05d2\u05e8\u05e1\u05d0\u05d5\u05ea \u05d1\u05d8\u05d0" + }, + "title": "\u05d4\u05d2\u05d3\u05e8\u05d4" + } } } } \ No newline at end of file diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index fef6d24a9bd..e37a6c8893e 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -2,7 +2,7 @@ import logging import math -from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -20,9 +20,20 @@ _LOGGER = logging.getLogger(__name__) DEV_TYPE_TO_HA = { "LV-PUR131S": "fan", + "LV-RH131S": "fan", # Alt ID Model LV-PUR131S "Core200S": "fan", + "LAP-C201S-AUSR": "fan", # Alt ID Model Core200S + "LAP-C202S-WUSR": "fan", # Alt ID Model Core200S "Core300S": "fan", + "LAP-C301S-WJP": "fan", # Alt ID Model Core300S "Core400S": "fan", + "LAP-C401S-WJP": "fan", # Alt ID Model Core400S + "LAP-C401S-WUSR": "fan", # Alt ID Model Core400S + "LAP-C401S-WAAA": "fan", # Alt ID Model Core400S + "Core600S": "fan", + "LAP-C601S-WUS": "fan", # Alt ID Model Core600S + "LAP-C601S-WUSR": "fan", # Alt ID Model Core600S + "LAP-C601S-WEU": "fan", # Alt ID Model Core600S } FAN_MODE_AUTO = "auto" @@ -30,15 +41,37 @@ FAN_MODE_SLEEP = "sleep" PRESET_MODES = { "LV-PUR131S": [FAN_MODE_AUTO, FAN_MODE_SLEEP], + "LV-RH131S": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model LV-PUR131S "Core200S": [FAN_MODE_SLEEP], + "LAP-C201S-AUSR": [FAN_MODE_SLEEP], # Alt ID Model Core200S + "LAP-C202S-WUSR": [FAN_MODE_SLEEP], # Alt ID Model Core200S "Core300S": [FAN_MODE_AUTO, FAN_MODE_SLEEP], + "LAP-C301S-WJP": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core300S "Core400S": [FAN_MODE_AUTO, FAN_MODE_SLEEP], + "LAP-C401S-WJP": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core400S + "LAP-C401S-WUSR": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core400S + "LAP-C401S-WAAA": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core400S + "Core600S": [FAN_MODE_AUTO, FAN_MODE_SLEEP], + "LAP-C601S-WUS": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core600S + "LAP-C601S-WUSR": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core600S + "LAP-C601S-WEU": [FAN_MODE_AUTO, FAN_MODE_SLEEP], # Alt ID Model Core600S } SPEED_RANGE = { # off is not included "LV-PUR131S": (1, 3), + "LV-RH131S": (1, 3), # ALt ID Model LV-PUR131S "Core200S": (1, 3), + "LAP-C201S-AUSR": (1, 3), # ALt ID Model Core200S + "LAP-C202S-WUSR": (1, 3), # ALt ID Model Core200S "Core300S": (1, 3), + "LAP-C301S-WJP": (1, 3), # ALt ID Model Core300S "Core400S": (1, 4), + "LAP-C401S-WJP": (1, 4), # ALt ID Model Core400S + "LAP-C401S-WUSR": (1, 4), # ALt ID Model Core400S + "LAP-C401S-WAAA": (1, 4), # ALt ID Model Core400S + "Core600S": (1, 4), + "LAP-C601S-WUS": (1, 4), # ALt ID Model Core600S + "LAP-C601S-WUSR": (1, 4), # ALt ID Model Core600S + "LAP-C601S-WEU": (1, 4), # ALt ID Model Core600S } @@ -80,16 +113,13 @@ def _setup_entities(devices, async_add_entities): class VeSyncFanHA(VeSyncDevice, FanEntity): """Representation of a VeSync fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + def __init__(self, fan): """Initialize the VeSync fan device.""" super().__init__(fan) self.smartfan = fan - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_SET_SPEED - @property def percentage(self): """Return the current speed.""" diff --git a/homeassistant/components/vesync/light.py b/homeassistant/components/vesync/light.py index 7fd682e1943..d01319ff0e7 100644 --- a/homeassistant/components/vesync/light.py +++ b/homeassistant/components/vesync/light.py @@ -4,8 +4,7 @@ import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -88,7 +87,7 @@ class VeSyncBaseLight(VeSyncDevice, LightEntity): """Turn the device on.""" attribute_adjustment_only = False # set white temperature - if self.color_mode in (COLOR_MODE_COLOR_TEMP,) and ATTR_COLOR_TEMP in kwargs: + if self.color_mode == ColorMode.COLOR_TEMP and ATTR_COLOR_TEMP in kwargs: # get white temperature from HA data color_temp = int(kwargs[ATTR_COLOR_TEMP]) # ensure value between min-max supported Mireds @@ -108,7 +107,7 @@ class VeSyncBaseLight(VeSyncDevice, LightEntity): attribute_adjustment_only = True # set brightness level if ( - self.color_mode in (COLOR_MODE_BRIGHTNESS, COLOR_MODE_COLOR_TEMP) + self.color_mode in (ColorMode.BRIGHTNESS, ColorMode.COLOR_TEMP) and ATTR_BRIGHTNESS in kwargs ): # get brightness from HA data @@ -133,20 +132,16 @@ class VeSyncBaseLight(VeSyncDevice, LightEntity): class VeSyncDimmableLightHA(VeSyncBaseLight, LightEntity): """Representation of a VeSync dimmable light device.""" - @property - def color_mode(self): - """Set color mode for this entity.""" - return COLOR_MODE_BRIGHTNESS - - @property - def supported_color_modes(self): - """Flag supported color_modes (in an array format).""" - return [COLOR_MODE_BRIGHTNESS] + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} class VeSyncTunableWhiteLightHA(VeSyncBaseLight, LightEntity): """Representation of a VeSync Tunable White Light device.""" + _attr_color_mode = ColorMode.COLOR_TEMP + _attr_supported_color_modes = {ColorMode.COLOR_TEMP} + @property def color_temp(self): """Get device white temperature.""" @@ -183,13 +178,3 @@ class VeSyncTunableWhiteLightHA(VeSyncBaseLight, LightEntity): def max_mireds(self): """Set device warmest white temperature.""" return 370 # 370 Mireds ( 1,000,000 divided by 2700 Kelvin = 370 Mireds) - - @property - def color_mode(self): - """Set color mode for this entity.""" - return COLOR_MODE_COLOR_TEMP - - @property - def supported_color_modes(self): - """Flag supported color_modes (in an array format).""" - return [COLOR_MODE_COLOR_TEMP] diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index 2637cfaa746..c93e070a484 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -3,7 +3,7 @@ "name": "VeSync", "documentation": "https://www.home-assistant.io/integrations/vesync", "codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"], - "requirements": ["pyvesync==1.4.2"], + "requirements": ["pyvesync==2.0.2"], "config_flow": true, "iot_class": "cloud_polling", "loggers": ["pyvesync"] diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 02998343744..4773101f1b9 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -1,4 +1,6 @@ """Viessmann ViCare climate device.""" +from __future__ import annotations + from contextlib import suppress import logging @@ -12,16 +14,12 @@ import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - HVAC_MODE_AUTO, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, PRESET_COMFORT, PRESET_ECO, PRESET_NONE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -72,22 +70,20 @@ VICARE_HOLD_MODE_OFF = "off" VICARE_TEMP_HEATING_MIN = 3 VICARE_TEMP_HEATING_MAX = 37 -SUPPORT_FLAGS_HEATING = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - VICARE_TO_HA_HVAC_HEATING = { - VICARE_MODE_DHW: HVAC_MODE_OFF, - VICARE_MODE_HEATING: HVAC_MODE_HEAT, - VICARE_MODE_DHWANDHEATING: HVAC_MODE_AUTO, - VICARE_MODE_DHWANDHEATINGCOOLING: HVAC_MODE_AUTO, - VICARE_MODE_FORCEDREDUCED: HVAC_MODE_OFF, - VICARE_MODE_FORCEDNORMAL: HVAC_MODE_HEAT, - VICARE_MODE_OFF: HVAC_MODE_OFF, + VICARE_MODE_DHW: HVACMode.OFF, + VICARE_MODE_HEATING: HVACMode.HEAT, + VICARE_MODE_DHWANDHEATING: HVACMode.AUTO, + VICARE_MODE_DHWANDHEATINGCOOLING: HVACMode.AUTO, + VICARE_MODE_FORCEDREDUCED: HVACMode.OFF, + VICARE_MODE_FORCEDNORMAL: HVACMode.HEAT, + VICARE_MODE_OFF: HVACMode.OFF, } HA_TO_VICARE_HVAC_HEATING = { - HVAC_MODE_HEAT: VICARE_MODE_FORCEDNORMAL, - HVAC_MODE_OFF: VICARE_MODE_FORCEDREDUCED, - HVAC_MODE_AUTO: VICARE_MODE_DHWANDHEATING, + HVACMode.HEAT: VICARE_MODE_FORCEDNORMAL, + HVACMode.OFF: VICARE_MODE_FORCEDREDUCED, + HVACMode.AUTO: VICARE_MODE_DHWANDHEATING, } VICARE_TO_HA_PRESET_HEATING = { @@ -151,6 +147,10 @@ async def async_setup_entry( class ViCareClimate(ClimateEntity): """Representation of the ViCare heating climate device.""" + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + def __init__(self, name, api, circuit, device_config, heating_type): """Initialize the climate device.""" self._name = name @@ -249,11 +249,6 @@ class ViCareClimate(ClimateEntity): except PyViCareInvalidDataError as invalid_data_exception: _LOGGER.error("Invalid data from Vicare server: %s", invalid_data_exception) - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS_HEATING - @property def name(self): """Return the name of the climate device.""" @@ -275,11 +270,11 @@ class ViCareClimate(ClimateEntity): return self._target_temperature @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode | None: """Return current hvac mode.""" return VICARE_TO_HA_HVAC_HEATING.get(self._current_mode) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set a new hvac mode on the ViCare API.""" vicare_mode = HA_TO_VICARE_HVAC_HEATING.get(hvac_mode) if vicare_mode is None: @@ -291,16 +286,16 @@ class ViCareClimate(ClimateEntity): self._circuit.setMode(vicare_mode) @property - def hvac_modes(self): + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac modes.""" return list(HA_TO_VICARE_HVAC_HEATING) @property - def hvac_action(self): + def hvac_action(self) -> HVACAction: """Return the current hvac action.""" if self._current_action: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return HVACAction.HEATING + return HVACAction.IDLE @property def min_temp(self): diff --git a/homeassistant/components/vicare/diagnostics.py b/homeassistant/components/vicare/diagnostics.py new file mode 100644 index 00000000000..b4c0032037c --- /dev/null +++ b/homeassistant/components/vicare/diagnostics.py @@ -0,0 +1,29 @@ +"""Diagnostics support for ViCare.""" +from __future__ import annotations + +import json +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import DOMAIN, VICARE_DEVICE_CONFIG + +TO_REDACT = {CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + # Currently we only support a single device + device = hass.data[DOMAIN][entry.entry_id][VICARE_DEVICE_CONFIG] + data: dict[str, Any] = json.loads( + await hass.async_add_executor_job(device.dump_secure) + ) + return { + "entry": async_redact_data(entry.as_dict(), TO_REDACT), + "data": data, + } diff --git a/homeassistant/components/vicare/strings.json b/homeassistant/components/vicare/strings.json index 28560ca41f3..d54956f3e10 100644 --- a/homeassistant/components/vicare/strings.json +++ b/homeassistant/components/vicare/strings.json @@ -3,7 +3,6 @@ "flow_title": "{name} ({host})", "step": { "user": { - "title": "{name}", "description": "Set up ViCare integration. To generate API key go to https://developer.viessmann.com", "data": { "username": "[%key:common::config_flow::data::email%]", diff --git a/homeassistant/components/vicare/translations/fr.json b/homeassistant/components/vicare/translations/fr.json index 260dbb25006..b3dce5ec826 100644 --- a/homeassistant/components/vicare/translations/fr.json +++ b/homeassistant/components/vicare/translations/fr.json @@ -18,7 +18,7 @@ "scan_interval": "Intervalle de balayage (secondes)", "username": "Courriel" }, - "description": "Configurer l'int\u00e9gration ViCare. Pour g\u00e9n\u00e9rer une cl\u00e9 API se rendre sur https://developer.viessmann.com", + "description": "Configurer l'int\u00e9gration ViCare. Pour g\u00e9n\u00e9rer une cl\u00e9 d'API, rendez-vous sur https://developer.viessmann.com", "title": "{name}" } } diff --git a/homeassistant/components/vicare/translations/hu.json b/homeassistant/components/vicare/translations/hu.json index 4dca080307d..0e69a395a90 100644 --- a/homeassistant/components/vicare/translations/hu.json +++ b/homeassistant/components/vicare/translations/hu.json @@ -13,7 +13,7 @@ "data": { "client_id": "API kulcs", "heating_type": "F\u0171t\u00e9s t\u00edpusa", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "scan_interval": "Beolvas\u00e1si id\u0151k\u00f6z (m\u00e1sodperc)", "username": "E-mail" diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index e1e44188d28..6f9262200ec 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -10,8 +10,8 @@ from PyViCare.PyViCareUtils import ( import requests from homeassistant.components.water_heater import ( - SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, + WaterHeaterEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -45,8 +45,6 @@ VICARE_TEMP_WATER_MAX = 60 OPERATION_MODE_ON = "on" OPERATION_MODE_OFF = "off" -SUPPORT_FLAGS_HEATER = SUPPORT_TARGET_TEMPERATURE - VICARE_TO_HA_HVAC_DHW = { VICARE_MODE_DHW: OPERATION_MODE_ON, VICARE_MODE_DHWANDHEATING: OPERATION_MODE_ON, @@ -101,6 +99,8 @@ async def async_setup_entry( class ViCareWater(WaterHeaterEntity): """Representation of the ViCare domestic hot water device.""" + _attr_supported_features = WaterHeaterEntityFeature.TARGET_TEMPERATURE + def __init__(self, name, api, circuit, device_config, heating_type): """Initialize the DHW water_heater device.""" self._name = name @@ -155,11 +155,6 @@ class ViCareWater(WaterHeaterEntity): "configuration_url": "https://developer.viessmann.com/", } - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_FLAGS_HEATER - @property def name(self): """Return the name of the water_heater device.""" diff --git a/homeassistant/components/vivotek/camera.py b/homeassistant/components/vivotek/camera.py index c8b9f3676f1..c25e96f161e 100644 --- a/homeassistant/components/vivotek/camera.py +++ b/homeassistant/components/vivotek/camera.py @@ -4,7 +4,7 @@ from __future__ import annotations from libpyvivotek import VivotekCamera import voluptuous as vol -from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, CameraEntityFeature from homeassistant.const import ( CONF_AUTHENTICATION, CONF_IP_ADDRESS, @@ -76,6 +76,8 @@ def setup_platform( class VivotekCam(Camera): """A Vivotek IP camera.""" + _attr_supported_features = CameraEntityFeature.STREAM + def __init__(self, config, cam, stream_source): """Initialize a Vivotek camera.""" super().__init__() @@ -87,11 +89,6 @@ class VivotekCam(Camera): self._name = config[CONF_NAME] self._stream_source = stream_source - @property - def supported_features(self): - """Return supported features for this camera.""" - return SUPPORT_STREAM - @property def frame_interval(self): """Return the interval between frames of the mjpeg stream.""" diff --git a/homeassistant/components/vizio/const.py b/homeassistant/components/vizio/const.py index bcfc38950d3..9615032c097 100644 --- a/homeassistant/components/vizio/const.py +++ b/homeassistant/components/vizio/const.py @@ -5,16 +5,9 @@ from pyvizio.const import ( ) import voluptuous as vol -from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerDeviceClass, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_ACCESS_TOKEN, @@ -48,7 +41,7 @@ CONF_NAME_SPACE = "NAME_SPACE" CONF_MESSAGE = "MESSAGE" CONF_VOLUME_STEP = "volume_step" -DEFAULT_DEVICE_CLASS = DEVICE_CLASS_TV +DEFAULT_DEVICE_CLASS = MediaPlayerDeviceClass.TV DEFAULT_NAME = "Vizio SmartCast" DEFAULT_TIMEOUT = 8 DEFAULT_VOLUME_STEP = 1 @@ -56,21 +49,26 @@ DEFAULT_VOLUME_STEP = 1 DEVICE_ID = "pyvizio" DOMAIN = "vizio" -ICON = {DEVICE_CLASS_TV: "mdi:television", DEVICE_CLASS_SPEAKER: "mdi:speaker"} +ICON = { + MediaPlayerDeviceClass.TV: "mdi:television", + MediaPlayerDeviceClass.SPEAKER: "mdi:speaker", +} COMMON_SUPPORTED_COMMANDS = ( - SUPPORT_SELECT_SOURCE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_STEP + MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP ) SUPPORTED_COMMANDS = { - DEVICE_CLASS_SPEAKER: COMMON_SUPPORTED_COMMANDS, - DEVICE_CLASS_TV: ( - COMMON_SUPPORTED_COMMANDS | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK + MediaPlayerDeviceClass.SPEAKER: COMMON_SUPPORTED_COMMANDS, + MediaPlayerDeviceClass.TV: ( + COMMON_SUPPORTED_COMMANDS + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PREVIOUS_TRACK ), } @@ -83,8 +81,8 @@ VIZIO_MUTE = "mute" # Since Vizio component relies on device class, this dict will ensure that changes to # the values of DEVICE_CLASS_SPEAKER or DEVICE_CLASS_TV don't require changes to pyvizio. VIZIO_DEVICE_CLASSES = { - DEVICE_CLASS_SPEAKER: VIZIO_DEVICE_CLASS_SPEAKER, - DEVICE_CLASS_TV: VIZIO_DEVICE_CLASS_TV, + MediaPlayerDeviceClass.SPEAKER: VIZIO_DEVICE_CLASS_SPEAKER, + MediaPlayerDeviceClass.TV: VIZIO_DEVICE_CLASS_TV, } VIZIO_SCHEMA = { @@ -92,7 +90,9 @@ VIZIO_SCHEMA = { vol.Optional(CONF_ACCESS_TOKEN): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.All( - cv.string, vol.Lower, vol.In([DEVICE_CLASS_TV, DEVICE_CLASS_SPEAKER]) + cv.string, + vol.Lower, + vol.In([MediaPlayerDeviceClass.TV, MediaPlayerDeviceClass.SPEAKER]), ), vol.Optional(CONF_VOLUME_STEP, default=DEFAULT_VOLUME_STEP): vol.All( vol.Coerce(int), vol.Range(min=1, max=10) diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index a076a995f7b..b2d33551020 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -10,9 +10,9 @@ from pyvizio.api.apps import find_app_name from pyvizio.const import APP_HOME, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_APP from homeassistant.components.media_player import ( - SUPPORT_SELECT_SOUND_MODE, MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -229,7 +229,9 @@ class VizioDevice(MediaPlayerEntity): self._attr_is_volume_muted = None if VIZIO_SOUND_MODE in audio_settings: - self._attr_supported_features |= SUPPORT_SELECT_SOUND_MODE + self._attr_supported_features |= ( + MediaPlayerEntityFeature.SELECT_SOUND_MODE + ) self._attr_sound_mode = audio_settings[VIZIO_SOUND_MODE] if not self._attr_sound_mode_list: self._attr_sound_mode_list = await self._device.get_setting_options( @@ -238,8 +240,10 @@ class VizioDevice(MediaPlayerEntity): log_api_exception=False, ) else: - # Explicitly remove SUPPORT_SELECT_SOUND_MODE from supported features - self._attr_supported_features &= ~SUPPORT_SELECT_SOUND_MODE + # Explicitly remove MediaPlayerEntityFeature.SELECT_SOUND_MODE from supported features + self._attr_supported_features &= ( + ~MediaPlayerEntityFeature.SELECT_SOUND_MODE + ) if input_ := await self._device.get_current_input(log_api_exception=False): self._current_input = input_ diff --git a/homeassistant/components/vizio/translations/hu.json b/homeassistant/components/vizio/translations/hu.json index bb619e359c0..908dfad8f76 100644 --- a/homeassistant/components/vizio/translations/hu.json +++ b/homeassistant/components/vizio/translations/hu.json @@ -31,7 +31,7 @@ "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", "device_class": "Eszk\u00f6zt\u00edpus", "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "A Hozz\u00e1f\u00e9r\u00e9si token csak t\u00e9v\u00e9khez sz\u00fcks\u00e9ges. Ha TV -t konfigur\u00e1l, \u00e9s m\u00e9g nincs Hozz\u00e1f\u00e9r\u00e9si token , hagyja \u00fcresen a p\u00e1ros\u00edt\u00e1si folyamathoz.", "title": "VIZIO SmartCast Eszk\u00f6z" diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index 77e2c17bedf..10903295f95 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -6,16 +6,12 @@ import logging import vlc import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -28,15 +24,6 @@ _LOGGER = logging.getLogger(__name__) CONF_ARGUMENTS = "arguments" DEFAULT_NAME = "Vlc" -SUPPORT_VLC = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_STOP -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_ARGUMENTS, default=""): cv.string, @@ -60,6 +47,15 @@ def setup_platform( class VlcDevice(MediaPlayerEntity): """Representation of a vlc player.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP + ) + def __init__(self, name, arguments): """Initialize the vlc device.""" self._instance = vlc.Instance(arguments) @@ -112,11 +108,6 @@ class VlcDevice(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._muted - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_VLC - @property def media_content_type(self): """Content type of current playing media.""" diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 99b37f97e0c..9d4e0a5a7a8 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -14,23 +14,10 @@ from homeassistant.components import media_source from homeassistant.components.media_player import ( BrowseMedia, MediaPlayerEntity, + MediaPlayerEntityFeature, async_process_play_media_url, ) -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SEEK, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant @@ -44,21 +31,6 @@ from .const import DATA_AVAILABLE, DATA_VLC, DEFAULT_NAME, DOMAIN, LOGGER MAX_VOLUME = 500 -SUPPORT_VLC = ( - SUPPORT_CLEAR_PLAYLIST - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_SEEK - | SUPPORT_SHUFFLE_SET - | SUPPORT_STOP - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET - | SUPPORT_BROWSE_MEDIA -) - _T = TypeVar("_T", bound="VlcDevice") _P = ParamSpec("_P") @@ -99,6 +71,21 @@ def catch_vlc_errors( class VlcDevice(MediaPlayerEntity): """Representation of a vlc player.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) + def __init__( self, config_entry: ConfigEntry, vlc: Client, name: str, available: bool ) -> None: @@ -214,11 +201,6 @@ class VlcDevice(MediaPlayerEntity): """Boolean if volume is currently muted.""" return self._muted - @property - def supported_features(self) -> int: - """Flag media player features that are supported.""" - return SUPPORT_VLC - @property def media_content_type(self) -> str: """Content type of current playing media.""" diff --git a/homeassistant/components/vlc_telnet/translations/hu.json b/homeassistant/components/vlc_telnet/translations/hu.json index 0a76fc089ed..bac42b4d4c3 100644 --- a/homeassistant/components/vlc_telnet/translations/hu.json +++ b/homeassistant/components/vlc_telnet/translations/hu.json @@ -26,7 +26,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "port": "Port" } diff --git a/homeassistant/components/volumio/media_player.py b/homeassistant/components/volumio/media_player.py index 75a84b4f94a..ec4931ce3bc 100644 --- a/homeassistant/components/volumio/media_player.py +++ b/homeassistant/components/volumio/media_player.py @@ -6,26 +6,14 @@ Volumio rest API: https://volumio.github.io/docs/API/REST_API.html from datetime import timedelta import json -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, REPEAT_MODE_ALL, REPEAT_MODE_OFF, - SUPPORT_BROWSE_MEDIA, - SUPPORT_CLEAR_PLAYLIST, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SEEK, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -43,24 +31,6 @@ from homeassistant.util import Throttle from .browse_media import browse_node, browse_top_level from .const import DATA_INFO, DATA_VOLUMIO, DOMAIN -SUPPORT_VOLUMIO = ( - SUPPORT_PAUSE - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_SEEK - | SUPPORT_STOP - | SUPPORT_PLAY - | SUPPORT_PLAY_MEDIA - | SUPPORT_VOLUME_STEP - | SUPPORT_SELECT_SOURCE - | SUPPORT_REPEAT_SET - | SUPPORT_SHUFFLE_SET - | SUPPORT_CLEAR_PLAYLIST - | SUPPORT_BROWSE_MEDIA -) - PLAYLIST_UPDATE_INTERVAL = timedelta(seconds=15) @@ -84,6 +54,24 @@ async def async_setup_entry( class Volumio(MediaPlayerEntity): """Volumio Player Object.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.SEEK + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.CLEAR_PLAYLIST + | MediaPlayerEntityFeature.BROWSE_MEDIA + ) + def __init__(self, volumio, uid, name, info): """Initialize the media player.""" self._volumio = volumio @@ -203,11 +191,6 @@ class Volumio(MediaPlayerEntity): """Name of the current input source.""" return self._currentplaylist - @property - def supported_features(self): - """Flag of media commands that are supported.""" - return SUPPORT_VOLUMIO - async def async_media_next_track(self): """Send media_next command to media player.""" await self._volumio.next() diff --git a/homeassistant/components/vulcan/__init__.py b/homeassistant/components/vulcan/__init__.py index 37210778b1c..430819c8f4c 100644 --- a/homeassistant/components/vulcan/__init__.py +++ b/homeassistant/components/vulcan/__init__.py @@ -61,7 +61,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" await hass.data[DOMAIN][entry.entry_id].close() for platform in PLATFORMS: diff --git a/homeassistant/components/vulcan/calendar.py b/homeassistant/components/vulcan/calendar.py index 9c853b03197..2e9e79063f9 100644 --- a/homeassistant/components/vulcan/calendar.py +++ b/homeassistant/components/vulcan/calendar.py @@ -7,10 +7,13 @@ from aiohttp import ClientConnectorError from vulcan._utils import VulcanAPIException from homeassistant.components.calendar import ENTITY_ID_FORMAT, CalendarEventDevice +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_SCAN_INTERVAL +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.util import Throttle, dt @@ -21,7 +24,11 @@ from .fetch_data import get_lessons, get_student_info _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the calendar platform for event devices.""" VulcanCalendarData.MIN_TIME_BETWEEN_UPDATES = timedelta( minutes=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) diff --git a/homeassistant/components/vulcan/translations/bg.json b/homeassistant/components/vulcan/translations/bg.json new file mode 100644 index 00000000000..27893871c14 --- /dev/null +++ b/homeassistant/components/vulcan/translations/bg.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e" + }, + "error": { + "cannot_connect": "\u0413\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435\u0442\u043e - \u043c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0432\u0430\u0448\u0430\u0442\u0430 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0432\u0440\u044a\u0437\u043a\u0430" + }, + "step": { + "select_saved_credentials": { + "data": { + "credentials": "\u0412\u0445\u043e\u0434" + } + } + } + }, + "options": { + "error": { + "error": "\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "init": { + "data": { + "message_notify": "\u041f\u043e\u043a\u0430\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 \u0438\u0437\u0432\u0435\u0441\u0442\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0432\u0430\u043d\u0435 \u043d\u0430 \u043d\u043e\u0432\u043e \u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u0435", + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043d\u0430 \u043e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435 (\u0432 \u043c\u0438\u043d\u0443\u0442\u0438)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/ca.json b/homeassistant/components/vulcan/translations/ca.json new file mode 100644 index 00000000000..505bcb1d326 --- /dev/null +++ b/homeassistant/components/vulcan/translations/ca.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Ja s'han afegit tots els alumnes.", + "already_configured": "Ja s'ha afegit aquest alumne.", + "reauth_successful": "Re-autenticaci\u00f3 exitosa" + }, + "error": { + "cannot_connect": "Error de connexi\u00f3, comprova la teva connexi\u00f3 a Internet", + "expired_credentials": "Credencials caducades, crea'n de noves a la p\u00e0gina de registre de l'aplicaci\u00f3 m\u00f2bil de Vulcan", + "expired_token": "Token caducat, genera un nou token", + "invalid_pin": "PIN inv\u00e0lid", + "invalid_symbol": "S\u00edmbol inv\u00e0lid", + "invalid_token": "Token inv\u00e0lid", + "unknown": "S'ha produ\u00eft un error desconegut" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Utilitza les credencials desades" + }, + "description": "Afegeix un altre alumne." + }, + "auth": { + "data": { + "pin": "PIN", + "region": "S\u00edmbol", + "token": "Token" + }, + "description": "Inicia sessi\u00f3 al teu compte de Vulcan mitjan\u00e7ant la p\u00e0gina de registre de l'aplicaci\u00f3 m\u00f2bil." + }, + "reauth": { + "data": { + "pin": "PIN", + "region": "S\u00edmbol", + "token": "Token" + }, + "description": "Inicia sessi\u00f3 al teu compte de Vulcan mitjan\u00e7ant la p\u00e0gina de registre de l'aplicaci\u00f3 m\u00f2bil." + }, + "select_saved_credentials": { + "data": { + "credentials": "Inici de sessi\u00f3" + }, + "description": "Selecciona credencials desades." + }, + "select_student": { + "data": { + "student_name": "Selecciona l'alumne" + }, + "description": "Selecciona l'alumne, pots afegir m\u00e9s alumnes tornant a afegir la integraci\u00f3 de nou." + } + } + }, + "options": { + "error": { + "error": "S'ha produ\u00eft un error" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Mostra les notificacions sobre les darreres assist\u00e8ncies", + "grade_notify": "Mostra notificacions de les \u00faltimes qualificacions", + "message_notify": "Mostra notificacions quan es rebi un missatge nou", + "scan_interval": "Interval d'actualitzaci\u00f3 (minuts)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/de.json b/homeassistant/components/vulcan/translations/de.json new file mode 100644 index 00000000000..23d5032f5fa --- /dev/null +++ b/homeassistant/components/vulcan/translations/de.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Alle Sch\u00fcler wurden bereits hinzugef\u00fcgt.", + "already_configured": "Dieser Sch\u00fcler wurde bereits hinzugef\u00fcgt.", + "reauth_successful": "Reauth erfolgreich" + }, + "error": { + "cannot_connect": "Verbindungsfehler - Bitte \u00fcberpr\u00fcfe deine Internetverbindung", + "expired_credentials": "Abgelaufene Anmeldedaten - bitte erstelle neue auf der Registrierungsseite der Vulcan Mobile App", + "expired_token": "Abgelaufener Token - bitte generiere einen neuen Token", + "invalid_pin": "Ung\u00fcltige PIN", + "invalid_symbol": "Ung\u00fcltiges Symbol", + "invalid_token": "Ung\u00fcltiges Token", + "unknown": "Unbekannter Fehler ist aufgetreten" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Gespeicherte Anmeldeinformationen verwenden" + }, + "description": "F\u00fcge einen weiteren Sch\u00fcler hinzu." + }, + "auth": { + "data": { + "pin": "PIN", + "region": "Symbol", + "token": "Token" + }, + "description": "Melde dich bei deinem Vulcan-Konto \u00fcber die Registrierungsseite der mobilen App an." + }, + "reauth": { + "data": { + "pin": "PIN", + "region": "Symbol", + "token": "Token" + }, + "description": "Melde dich bei deinem Vulcan-Konto \u00fcber die Registrierungsseite der mobilen App an." + }, + "select_saved_credentials": { + "data": { + "credentials": "Anmelden" + }, + "description": "W\u00e4hle gespeicherte Anmeldeinformationen aus." + }, + "select_student": { + "data": { + "student_name": "Sch\u00fcler ausw\u00e4hlen" + }, + "description": "W\u00e4hle Sch\u00fcler aus, Du kannst weitere Sch\u00fcler hinzuf\u00fcgen, indem du die Integration erneut hinzuf\u00fcgst." + } + } + }, + "options": { + "error": { + "error": "Ein Fehler ist aufgetreten" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Benachrichtigungen \u00fcber die neuesten Anwesenheitseintr\u00e4ge anzeigen", + "grade_notify": "Benachrichtigungen \u00fcber die neuesten Noten anzeigen", + "message_notify": "Benachrichtigungen anzeigen, wenn eine neue Nachricht eingegangen ist", + "scan_interval": "Aktualisierungsintervall (in Minuten)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/el.json b/homeassistant/components/vulcan/translations/el.json new file mode 100644 index 00000000000..bb77c88f8e5 --- /dev/null +++ b/homeassistant/components/vulcan/translations/el.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "\u038c\u03bb\u03bf\u03b9 \u03bf\u03b9 \u03bc\u03b1\u03b8\u03b7\u03c4\u03ad\u03c2 \u03ad\u03c7\u03bf\u03c5\u03bd \u03ae\u03b4\u03b7 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af.", + "already_configured": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03bc\u03b1\u03b8\u03b7\u03c4\u03ae\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af.", + "reauth_successful": "\u039f \u03b5\u03ba \u03bd\u03ad\u03bf\u03c5 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + }, + "error": { + "cannot_connect": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 - \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf \u0394\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf", + "expired_credentials": "\u039b\u03b7\u03b3\u03bc\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1 - \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03bd\u03ad\u03b1 \u03c3\u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac \u03c4\u03b7\u03c2 Vulcan", + "expired_token": "\u039b\u03b7\u03b3\u03bc\u03ad\u03bd\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc - \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bd\u03ad\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc", + "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf PIN", + "invalid_symbol": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03c3\u03cd\u03bc\u03b2\u03bf\u03bb\u03bf", + "invalid_token": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc", + "unknown": "\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03b7\u03c1\u03af\u03c9\u03bd" + }, + "description": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03ac\u03bb\u03bb\u03bf\u03c5 \u03bc\u03b1\u03b8\u03b7\u03c4\u03ae." + }, + "auth": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", + "region": "\u03a3\u03cd\u03bc\u03b2\u03bf\u03bb\u03bf", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" + }, + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Vulcan \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac." + }, + "reauth": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN", + "region": "\u03a3\u03cd\u03bc\u03b2\u03bf\u03bb\u03bf", + "token": "\u0394\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03cc" + }, + "description": "\u03a3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2 Vulcan \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae\u03c2 \u03b3\u03b9\u03b1 \u03ba\u03b9\u03bd\u03b7\u03c4\u03ac." + }, + "select_saved_credentials": { + "data": { + "credentials": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03c0\u03b9\u03c3\u03c4\u03b5\u03c5\u03c4\u03ae\u03c1\u03b9\u03b1." + }, + "select_student": { + "data": { + "student_name": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03bc\u03b1\u03b8\u03b7\u03c4\u03ae" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03b1\u03b8\u03b7\u03c4\u03ae, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03bf\u03c5\u03c2 \u03bc\u03b1\u03b8\u03b7\u03c4\u03ad\u03c2 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c4\u03bf\u03bd\u03c4\u03b1\u03c2 \u03be\u03b1\u03bd\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7." + } + } + }, + "options": { + "error": { + "error": "\u03a0\u03c1\u03bf\u03ad\u03ba\u03c5\u03c8\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "init": { + "data": { + "attendance_notify": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03b5\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ce\u03bd", + "grade_notify": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c0\u03b9\u03bf \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03bf\u03c5\u03c2 \u03b2\u03b1\u03b8\u03bc\u03bf\u03cd\u03c2", + "message_notify": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c9\u03bd \u03cc\u03c4\u03b1\u03bd \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03c4\u03b1\u03b9 \u03bd\u03ad\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1", + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03c3\u03b5 \u03bb\u03b5\u03c0\u03c4\u03ac)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/en.json b/homeassistant/components/vulcan/translations/en.json index abb3dce7c7f..48c054d2c15 100644 --- a/homeassistant/components/vulcan/translations/en.json +++ b/homeassistant/components/vulcan/translations/en.json @@ -1,69 +1,69 @@ { - "config": { - "abort": { - "already_configured": "That student has already been added.", - "all_student_already_configured": "All students have already been added.", - "reauth_successful": "Reauth successful" + "config": { + "abort": { + "all_student_already_configured": "All students have already been added.", + "already_configured": "That student has already been added.", + "reauth_successful": "Reauth successful" + }, + "error": { + "cannot_connect": "Connection error - please check your internet connection", + "expired_credentials": "Expired credentials - please create new on Vulcan mobile app registration page", + "expired_token": "Expired token - please generate a new token", + "invalid_pin": "Invalid pin", + "invalid_symbol": "Invalid symbol", + "invalid_token": "Invalid token", + "unknown": "Unknown error occurred" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Use saved credentials" + }, + "description": "Add another student." + }, + "auth": { + "data": { + "pin": "Pin", + "region": "Symbol", + "token": "Token" + }, + "description": "Login to your Vulcan Account using mobile app registration page." + }, + "reauth": { + "data": { + "pin": "Pin", + "region": "Symbol", + "token": "Token" + }, + "description": "Login to your Vulcan Account using mobile app registration page." + }, + "select_saved_credentials": { + "data": { + "credentials": "Login" + }, + "description": "Select saved credentials." + }, + "select_student": { + "data": { + "student_name": "Select student" + }, + "description": "Select student, you can add more students by adding integration again." + } + } }, - "error": { - "unknown": "Unknown error occurred", - "invalid_token": "Invalid token", - "expired_token": "Expired token - please generate a new token", - "invalid_pin": "Invalid pin", - "invalid_symbol": "Invalid symbol", - "expired_credentials": "Expired credentials - please create new on Vulcan mobile app registration page", - "cannot_connect": "Connection error - please check your internet connection" - }, - "step": { - "auth": { - "description": "Login to your Vulcan Account using mobile app registration page.", - "data": { - "token": "Token", - "region": "Symbol", - "pin": "Pin" + "options": { + "error": { + "error": "Error occurred" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Show notifications about the latest attendance entries", + "grade_notify": "Show notifications about the latest grades", + "message_notify": "Show notifications when new message received", + "scan_interval": "Update interval (in minutes)" + } + } } - }, - "reauth": { - "description": "Login to your Vulcan Account using mobile app registration page.", - "data": { - "token": "Token", - "region": "Symbol", - "pin": "Pin" - } - }, - "select_student": { - "description": "Select student, you can add more students by adding integration again.", - "data": { - "student_name": "Select student" - } - }, - "select_saved_credentials": { - "description": "Select saved credentials.", - "data": { - "credentials": "Login" - } - }, - "add_next_config_entry": { - "description": "Add another student.", - "data": { - "use_saved_credentials": "Use saved credentials" - } - } } - }, - "options": { - "error": { - "error": "Error occurred" - }, - "step": { - "init": { - "data": { - "message_notify": "Show notifications when new message received", - "attendance_notify": "Show notifications about the latest attendance entries", - "grade_notify": "Show notifications about the latest grades", - "scan_interval": "Update interval (in minutes)" - } - } - } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/et.json b/homeassistant/components/vulcan/translations/et.json new file mode 100644 index 00000000000..ab3156cc676 --- /dev/null +++ b/homeassistant/components/vulcan/translations/et.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "K\u00f5ik \u00f5pilased on juba lisatud.", + "already_configured": "See \u00f5pilane on juba lisatud.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "cannot_connect": "\u00dchendusviga - kontrolli oma interneti\u00fchendust", + "expired_credentials": "Aegunud mandaat \u2013 loo Vulcani mobiilirakenduse registreerimislehel uued", + "expired_token": "Token on aegunud - loo uus token.", + "invalid_pin": "Vigane PIN kood", + "invalid_symbol": "Vigane s\u00fcmbol", + "invalid_token": "Vigane token", + "unknown": "Ilmnes ootamatu t\u00f5rge" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Salvestatud identimisteabe kasutamine" + }, + "description": "Lisa veel \u00fcks \u00f5pilane." + }, + "auth": { + "data": { + "pin": "PIN kood", + "region": "S\u00fcmbol", + "token": "Token" + }, + "description": "Logi Vulcani kontole sisse mobiilirakenduse registreerimislehe kaudu." + }, + "reauth": { + "data": { + "pin": "PIN kood", + "region": "S\u00fcmbol", + "token": "Token" + }, + "description": "Logi Vulcani kontole sisse mobiilirakenduse registreerimislehe kaudu." + }, + "select_saved_credentials": { + "data": { + "credentials": "Sisselogimine" + }, + "description": "Vali salvestatud identimisteave." + }, + "select_student": { + "data": { + "student_name": "Vali \u00f5pilane" + }, + "description": "Vali \u00f5pilane, saad lisada rohkem \u00f5pilasi lisades sidumise veelkord." + } + } + }, + "options": { + "error": { + "error": "Ilmnes t\u00f5rge" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Kuva m\u00e4rguanded viimaste osalemiskirjete kohta", + "grade_notify": "Kuva m\u00e4rguanded viimaste hinnete kohta", + "message_notify": "Kuva teated uue s\u00f5numi saabumisel", + "scan_interval": "V\u00e4rskendamise intervall (minutites)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/fr.json b/homeassistant/components/vulcan/translations/fr.json new file mode 100644 index 00000000000..812c069223e --- /dev/null +++ b/homeassistant/components/vulcan/translations/fr.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Tous les \u00e9tudiants ont d\u00e9j\u00e0 \u00e9t\u00e9 ajout\u00e9s.", + "already_configured": "Cet \u00e9tudiant a d\u00e9j\u00e0 \u00e9t\u00e9 ajout\u00e9.", + "reauth_successful": "R\u00e9-authentification r\u00e9ussie" + }, + "error": { + "cannot_connect": "Erreur de connexion\u00a0; veuillez v\u00e9rifier votre connexion Internet", + "expired_credentials": "Identifiants expir\u00e9s\u00a0; veuillez en cr\u00e9er des nouveaux sur la page d'inscription de l'application mobile Vulcan", + "expired_token": "Jeton expir\u00e9\u00a0; veuillez g\u00e9n\u00e9rer un nouveau jeton", + "invalid_pin": "PIN non valide", + "invalid_symbol": "Symbole non valide", + "invalid_token": "Jeton non valide", + "unknown": "Une erreur inconnue est survenue" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Utiliser les informations d'identification enregistr\u00e9es" + }, + "description": "Ajoutez un autre \u00e9tudiant." + }, + "auth": { + "data": { + "pin": "PIN", + "region": "Symbole", + "token": "Jeton" + }, + "description": "Connectez-vous \u00e0 votre compte Vulcan en utilisant la page d'inscription de l'application mobile." + }, + "reauth": { + "data": { + "pin": "PIN", + "region": "Symbole", + "token": "Jeton" + }, + "description": "Connectez-vous \u00e0 votre compte Vulcan en utilisant la page d'inscription de l'application mobile." + }, + "select_saved_credentials": { + "data": { + "credentials": "Connexion" + }, + "description": "S\u00e9lectionnez les informations d'identification enregistr\u00e9es." + }, + "select_student": { + "data": { + "student_name": "S\u00e9lectionner un \u00e9tudiant" + }, + "description": "S\u00e9lectionnez l'\u00e9tudiant\u00a0; vous pouvez ajouter d'autres \u00e9tudiants en ajoutant \u00e0 nouveau l'int\u00e9gration." + } + } + }, + "options": { + "error": { + "error": "Une erreur est survenue" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Afficher les notifications au sujet des derni\u00e8res entr\u00e9es de pr\u00e9sence", + "grade_notify": "Afficher les notifications au sujet des derni\u00e8res notes", + "message_notify": "Afficher les notifications lors de la r\u00e9ception d'un nouveau message", + "scan_interval": "Intervalle de mise \u00e0 jour (en minutes)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/hu.json b/homeassistant/components/vulcan/translations/hu.json new file mode 100644 index 00000000000..9e138254ebe --- /dev/null +++ b/homeassistant/components/vulcan/translations/hu.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "M\u00e1r minden tanul\u00f3 hozz\u00e1adva.", + "already_configured": "Ezt a tanul\u00f3t m\u00e1r hozz\u00e1adt\u00e1k.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres" + }, + "error": { + "cannot_connect": "Csatlakoz\u00e1si hiba \u2013 ellen\u0151rizze az internetkapcsolatot", + "expired_credentials": "Lej\u00e1rt hiteles\u00edt\u0151 adatok - k\u00e9rj\u00fck, hozzon l\u00e9tre \u00fajakat a Vulcan mobilalkalmaz\u00e1s regisztr\u00e1ci\u00f3s oldal\u00e1n.", + "expired_token": "Lej\u00e1rt token \u2013 k\u00e9rj\u00fck, hozzon l\u00e9tre egy \u00fajat", + "invalid_pin": "\u00c9rv\u00e9nytelen PIN-k\u00f3d", + "invalid_symbol": "\u00c9rv\u00e9nytelen szimb\u00f3lum", + "invalid_token": "\u00c9rv\u00e9nytelen token", + "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Mentett hiteles\u00edt\u0151 adatok haszn\u00e1lata" + }, + "description": "Adjon hozz\u00e1 egy m\u00e1sik tanul\u00f3t." + }, + "auth": { + "data": { + "pin": "PIN", + "region": "Szimb\u00f3lum", + "token": "Token" + }, + "description": "Jelentkezzen be Vulcan fi\u00f3kj\u00e1ba a mobilalkalmaz\u00e1s regisztr\u00e1ci\u00f3s oldal\u00e1n." + }, + "reauth": { + "data": { + "pin": "PIN", + "region": "Szimb\u00f3lum", + "token": "Token" + }, + "description": "Jelentkezzen be Vulcan fi\u00f3kj\u00e1ba a mobilalkalmaz\u00e1s regisztr\u00e1ci\u00f3s oldal\u00e1n." + }, + "select_saved_credentials": { + "data": { + "credentials": "Bejelentkez\u00e9s" + }, + "description": "V\u00e1lassza ki a mentett hiteles\u00edt\u0151 adatokat." + }, + "select_student": { + "data": { + "student_name": "Tanul\u00f3 kiv\u00e1laszt\u00e1sa" + }, + "description": "V\u00e1lassza ki a tanul\u00f3t, tov\u00e1bbi tanul\u00f3kat vehet fel az integr\u00e1ci\u00f3 \u00fajb\u00f3li hozz\u00e1ad\u00e1s\u00e1val." + } + } + }, + "options": { + "error": { + "error": "Hiba t\u00f6rt\u00e9nt" + }, + "step": { + "init": { + "data": { + "attendance_notify": "\u00c9rtes\u00edt\u00e9sek megjelen\u00edt\u00e9se a legut\u00f3bbi r\u00e9szv\u00e9teli bejegyz\u00e9sekr\u0151l", + "grade_notify": "\u00c9rtes\u00edt\u00e9sek megjelen\u00edt\u00e9se a leg\u00fajabb oszt\u00e1lyzatokr\u00f3l", + "message_notify": "\u00c9rtes\u00edt\u00e9sek megjelen\u00edt\u00e9se \u00faj \u00fczenet \u00e9rkez\u00e9sekor", + "scan_interval": "Friss\u00edt\u00e9si id\u0151k\u00f6z (percben)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/id.json b/homeassistant/components/vulcan/translations/id.json new file mode 100644 index 00000000000..a15ed1772d2 --- /dev/null +++ b/homeassistant/components/vulcan/translations/id.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Semua siswa telah ditambahkan.", + "already_configured": "Siswa tersebut telah ditambahkan.", + "reauth_successful": "Otorisasi ulang berhasil" + }, + "error": { + "cannot_connect": "Kesalahan koneksi - periksa koneksi internet Anda", + "expired_credentials": "Kredensial kedaluwarsa - buat baru di halaman pendaftaran aplikasi seluler Vulcan", + "expired_token": "Token kedaluwarsa - buat token baru", + "invalid_pin": "PIN tidak valid", + "invalid_symbol": "Simbol tidak valid", + "invalid_token": "Token tidak valid", + "unknown": "Kesalahan tidak dikenal terjadi." + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Menggunakan kredensial yang disimpan" + }, + "description": "Tambahkan siswa lain." + }, + "auth": { + "data": { + "pin": "PIN", + "region": "Simbol", + "token": "Token" + }, + "description": "Masuk ke Akun Vulcan Anda menggunakan halaman pendaftaran aplikasi seluler." + }, + "reauth": { + "data": { + "pin": "PIN", + "region": "Simbol", + "token": "Token" + }, + "description": "Masuk ke Akun Vulcan Anda menggunakan halaman pendaftaran aplikasi seluler." + }, + "select_saved_credentials": { + "data": { + "credentials": "Masuk" + }, + "description": "Pilih kredensial yang disimpan." + }, + "select_student": { + "data": { + "student_name": "Pilih siswa" + }, + "description": "Pilih siswa, Anda dapat menambahkan lebih banyak siswa dengan menambahkan integrasi lagi." + } + } + }, + "options": { + "error": { + "error": "Terjadi kesalahan" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Tampilkan notifikasi tentang entri kehadiran terbaru", + "grade_notify": "Tampilkan notifikasi tentang nilai terbaru", + "message_notify": "Tampilkan saat pesan baru diterima", + "scan_interval": "Interval pembaruan (dalam menit)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/it.json b/homeassistant/components/vulcan/translations/it.json new file mode 100644 index 00000000000..72bb696a909 --- /dev/null +++ b/homeassistant/components/vulcan/translations/it.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Tutti gli studenti sono gi\u00e0 stati aggiunti.", + "already_configured": "Quello studente \u00e8 gi\u00e0 stato aggiunto.", + "reauth_successful": "Nuova autenticazione avvenuta" + }, + "error": { + "cannot_connect": "Errore di connessione: controlla la tua connessione Internet", + "expired_credentials": "Credenziali scadute: creane una nuova nella pagina di registrazione dell'applicazione mobile Vulcan", + "expired_token": "Token scaduto: genera un nuovo token", + "invalid_pin": "Pin non valido", + "invalid_symbol": "Simbolo non valido", + "invalid_token": "Token non valido", + "unknown": "Si \u00e8 verificato un errore sconosciuto" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Usa le credenziali salvate" + }, + "description": "Aggiungi un altro studente." + }, + "auth": { + "data": { + "pin": "PIN", + "region": "Simbolo", + "token": "Token" + }, + "description": "Accedi al tuo account Vulcan utilizzando la pagina di registrazione dell'applicazione mobile." + }, + "reauth": { + "data": { + "pin": "PIN", + "region": "Simbolo", + "token": "Token" + }, + "description": "Accedi al tuo account Vulcan utilizzando la pagina di registrazione dell'applicazione mobile." + }, + "select_saved_credentials": { + "data": { + "credentials": "Accesso" + }, + "description": "Seleziona le credenziali salvate." + }, + "select_student": { + "data": { + "student_name": "Seleziona studente" + }, + "description": "Seleziona studente, puoi aggiungere pi\u00f9 studenti aggiungendo nuovamente l'integrazione." + } + } + }, + "options": { + "error": { + "error": "Si \u00e8 verificato un errore" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Mostra le notifiche sulle ultime voci di partecipazione", + "grade_notify": "Mostra le notifiche sugli ultimi voti", + "message_notify": "Mostra le notifiche quando viene ricevuto un nuovo messaggio", + "scan_interval": "Intervallo di aggiornamento (in minuti)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/ja.json b/homeassistant/components/vulcan/translations/ja.json new file mode 100644 index 00000000000..98363f12f13 --- /dev/null +++ b/homeassistant/components/vulcan/translations/ja.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "\u3059\u3079\u3066\u306e\u751f\u5f92\u306f\u3059\u3067\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002", + "already_configured": "\u305d\u306e\u5b66\u751f\u306f\u3059\u3067\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002", + "reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f" + }, + "error": { + "cannot_connect": "\u63a5\u7d9a\u30a8\u30e9\u30fc - \u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u63a5\u7d9a\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044", + "expired_credentials": "\u6709\u52b9\u671f\u9650\u304c\u5207\u308c\u305f\u8a8d\u8a3c\u60c5\u5831 - Vulcan\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u306e\u767b\u9332\u30da\u30fc\u30b8\u3067\u65b0\u898f\u4f5c\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002", + "expired_token": "\u6709\u52b9\u671f\u9650\u5207\u308c\u306e\u30c8\u30fc\u30af\u30f3 - \u65b0\u3057\u3044\u30c8\u30fc\u30af\u30f3\u3092\u751f\u6210\u3057\u3066\u304f\u3060\u3055\u3044", + "invalid_pin": "\u7121\u52b9\u306a\u30d4\u30f3", + "invalid_symbol": "\u7121\u52b9\u306a\u30b7\u30f3\u30dc\u30eb", + "invalid_token": "\u7121\u52b9\u306a\u30c8\u30fc\u30af\u30f3", + "unknown": "\u4e0d\u660e\u306a\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "\u4fdd\u5b58\u3055\u308c\u305f\u8cc7\u683c\u60c5\u5831\u3092\u4f7f\u7528\u3059\u308b" + }, + "description": "\u5225\u306e\u751f\u5f92\u3092\u8ffd\u52a0\u3057\u307e\u3059\u3002" + }, + "auth": { + "data": { + "pin": "\u30d4\u30f3", + "region": "\u30b7\u30f3\u30dc\u30eb", + "token": "\u30c8\u30fc\u30af\u30f3" + }, + "description": "\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u767b\u9332\u30da\u30fc\u30b8\u3092\u4f7f\u7528\u3057\u3066\u3001Vulcan\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u307e\u3059\u3002" + }, + "reauth": { + "data": { + "pin": "\u30d4\u30f3", + "region": "\u30b7\u30f3\u30dc\u30eb", + "token": "\u30c8\u30fc\u30af\u30f3" + }, + "description": "\u30e2\u30d0\u30a4\u30eb\u30a2\u30d7\u30ea\u767b\u9332\u30da\u30fc\u30b8\u3092\u4f7f\u7528\u3057\u3066\u3001Vulcan\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u30ed\u30b0\u30a4\u30f3\u3057\u307e\u3059\u3002" + }, + "select_saved_credentials": { + "data": { + "credentials": "\u30ed\u30b0\u30a4\u30f3" + }, + "description": "\u4fdd\u5b58\u3055\u308c\u305f\u8cc7\u683c\u60c5\u5831\u3092\u9078\u629e\u3057\u307e\u3059\u3002" + }, + "select_student": { + "data": { + "student_name": "\u751f\u5f92\u3092\u9078\u629e(Select student)" + }, + "description": "\u751f\u5f92\u3092\u9078\u629e\u3057\u307e\u3059(Select student)\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u8ffd\u52a0\u3059\u308b\u3053\u3068\u3067\u3001\u3088\u308a\u591a\u304f\u306e\u751f\u5f92\u3092\u8ffd\u52a0\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002" + } + } + }, + "options": { + "error": { + "error": "\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f" + }, + "step": { + "init": { + "data": { + "attendance_notify": "\u6700\u65b0\u306e\u51fa\u5e2d\u30a8\u30f3\u30c8\u30ea\u306b\u95a2\u3059\u308b\u901a\u77e5\u3092\u8868\u793a\u3059\u308b", + "grade_notify": "\u6700\u65b0\u306e\u6210\u7e3e\u306b\u95a2\u3059\u308b\u901a\u77e5\u3092\u8868\u793a\u3059\u308b", + "message_notify": "\u65b0\u3057\u3044\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u53d7\u4fe1\u6642\u306b\u901a\u77e5\u3092\u8868\u793a\u3059\u308b", + "scan_interval": "\u66f4\u65b0\u9593\u9694(\u5206\u5358\u4f4d)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/nl.json b/homeassistant/components/vulcan/translations/nl.json new file mode 100644 index 00000000000..b05b32937c0 --- /dev/null +++ b/homeassistant/components/vulcan/translations/nl.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Alle studenten zijn al toegevoegd.", + "already_configured": "Die student is al toegevoegd.", + "reauth_successful": "Opnieuw verifi\u00ebren gelukt" + }, + "error": { + "cannot_connect": "Verbindingsfout - controleer uw internetverbinding", + "expired_credentials": "Verlopen inloggegevens - maak een nieuwe aan op de Vulcan mobiele app registratiepagina", + "expired_token": "Verlopen token - genereer een nieuw token", + "invalid_pin": "Ongeldige pin", + "invalid_symbol": "Ongeldig symbool", + "invalid_token": "Ongeldige Token", + "unknown": "Onbekende fout opgetreden" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Gebruik opgeslagen referenties" + }, + "description": "Voeg nog een student toe." + }, + "auth": { + "data": { + "pin": "Pin", + "region": "Symbool", + "token": "Token" + }, + "description": "Log in op uw Vulcan-account via de registratiepagina voor mobiele apps." + }, + "reauth": { + "data": { + "pin": "Pin", + "region": "Symbool", + "token": "Token" + }, + "description": "Log in op uw Vulcan-account via de registratiepagina voor mobiele apps." + }, + "select_saved_credentials": { + "data": { + "credentials": "Inloggen" + }, + "description": "Selecteer opgeslagen referenties." + }, + "select_student": { + "data": { + "student_name": "Selecteer student" + }, + "description": "Selecteer student, u kunt meer studenten toevoegen door integratie opnieuw toe te voegen." + } + } + }, + "options": { + "error": { + "error": "Er is een fout opgetreden." + }, + "step": { + "init": { + "data": { + "attendance_notify": "Toon meldingen over de laatste aanwezigheidsgegevens", + "grade_notify": "Toon meldingen over de laatste cijfers", + "message_notify": "Meldingen weergeven wanneer een nieuw bericht is ontvangen", + "scan_interval": "Update-interval (in minuten)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/no.json b/homeassistant/components/vulcan/translations/no.json new file mode 100644 index 00000000000..7cb8b68fa63 --- /dev/null +++ b/homeassistant/components/vulcan/translations/no.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Alle elever er allerede lagt til.", + "already_configured": "Den studenten er allerede lagt til.", + "reauth_successful": "Reauth vellykket" + }, + "error": { + "cannot_connect": "Tilkoblingsfeil - sjekk internettforbindelsen din", + "expired_credentials": "Utl\u00f8pt p\u00e5loggingsinformasjon - vennligst opprett ny p\u00e5 registreringssiden for Vulcan-mobilappen", + "expired_token": "Utl\u00f8pt token - generer et nytt token", + "invalid_pin": "Ugyldig pinkode", + "invalid_symbol": "Ugyldig symbol", + "invalid_token": "Ugyldig token", + "unknown": "Ukjent feil oppstod" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Bruk lagret legitimasjon" + }, + "description": "Legg til en annen student." + }, + "auth": { + "data": { + "pin": "Pin", + "region": "Symbol", + "token": "Token" + }, + "description": "Logg p\u00e5 Vulcan-kontoen din ved \u00e5 bruke registreringssiden for mobilappen." + }, + "reauth": { + "data": { + "pin": "Pin", + "region": "Symbol", + "token": "Token" + }, + "description": "Logg p\u00e5 Vulcan-kontoen din ved \u00e5 bruke registreringssiden for mobilappen." + }, + "select_saved_credentials": { + "data": { + "credentials": "P\u00e5logging" + }, + "description": "Velg lagret legitimasjon." + }, + "select_student": { + "data": { + "student_name": "Velg elev" + }, + "description": "Velg student, du kan legge til flere studenter ved \u00e5 legge til integrering p\u00e5 nytt." + } + } + }, + "options": { + "error": { + "error": "Det oppstod en feil" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Vis varsler om de siste fremm\u00f8teoppf\u00f8ringene", + "grade_notify": "Vis varsler om de nyeste vurderingene", + "message_notify": "Vis varsler n\u00e5r ny melding mottas", + "scan_interval": "Oppdateringsintervall (i minutter)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/pl.json b/homeassistant/components/vulcan/translations/pl.json new file mode 100644 index 00000000000..acbc51c6754 --- /dev/null +++ b/homeassistant/components/vulcan/translations/pl.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Wszyscy uczniowie zostali ju\u017c dodani.", + "already_configured": "Ten ucze\u0144 zosta\u0142 ju\u017c dodany.", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "expired_credentials": "Dane uwierzytelniaj\u0105ce wygas\u0142y \u2014 utw\u00f3rz nowe na stronie rejestracji aplikacji mobilnej Vulcan", + "expired_token": "Token wygas\u0142, wygeneruj nowy", + "invalid_pin": "Nieprawid\u0142owy kod PIN", + "invalid_symbol": "Nieprawid\u0142owy symbol", + "invalid_token": "Nieprawid\u0142owy token", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "U\u017cyj zapisanych danych uwierzytelniaj\u0105cych" + }, + "description": "Dodaj kolejnego ucznia." + }, + "auth": { + "data": { + "pin": "Kod pin", + "region": "Symbol", + "token": "Token" + }, + "description": "Zaloguj si\u0119 do swojego konta Vulcan za pomoc\u0105 strony rejestracji aplikacji mobilnej." + }, + "reauth": { + "data": { + "pin": "Kod pin", + "region": "Symbol", + "token": "Token" + }, + "description": "Zaloguj si\u0119 do swojego konta Vulcan za pomoc\u0105 strony rejestracji aplikacji mobilnej." + }, + "select_saved_credentials": { + "data": { + "credentials": "Login" + }, + "description": "Wybierz zapisane dane uwierzytelniaj\u0105ce." + }, + "select_student": { + "data": { + "student_name": "Wybierz ucznia" + }, + "description": "Wybierz ucznia. Mo\u017cesz doda\u0107 wi\u0119cej uczni\u00f3w, ponownie dodaj\u0105c integracj\u0119." + } + } + }, + "options": { + "error": { + "error": "Wyst\u0105pi\u0142 b\u0142\u0105d" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Poka\u017c powiadomienia o najnowszych wpisach o obecno\u015bci", + "grade_notify": "Poka\u017c powiadomienia o najnowszych ocenach", + "message_notify": "Pokazuj powiadomienia po otrzymaniu nowej wiadomo\u015bci", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (w minutach)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/pt-BR.json b/homeassistant/components/vulcan/translations/pt-BR.json new file mode 100644 index 00000000000..ecef34ff96b --- /dev/null +++ b/homeassistant/components/vulcan/translations/pt-BR.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "Todos os alunos j\u00e1 foram adicionados.", + "already_configured": "Esse aluno j\u00e1 foi adicionado.", + "reauth_successful": "Autentica\u00e7\u00e3o bem-sucedida" + }, + "error": { + "cannot_connect": "Erro de conex\u00e3o - verifique sua conex\u00e3o com a Internet", + "expired_credentials": "Credenciais expiradas - crie uma nova na p\u00e1gina de registro do aplicativo m\u00f3vel Vulcan", + "expired_token": "Token expirado - gere um novo token", + "invalid_pin": "Pin inv\u00e1lido", + "invalid_symbol": "S\u00edmbolo inv\u00e1lido", + "invalid_token": "Token inv\u00e1lido", + "unknown": "Ocorreu um erro desconhecido" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Usar credenciais salvas" + }, + "description": "Adicione outro aluno." + }, + "auth": { + "data": { + "pin": "Pin", + "region": "S\u00edmbolo", + "token": "Token" + }, + "description": "Fa\u00e7a login na sua conta Vulcan usando a p\u00e1gina de registro do aplicativo m\u00f3vel." + }, + "reauth": { + "data": { + "pin": "Pin", + "region": "S\u00edmbolo", + "token": "Token" + }, + "description": "Fa\u00e7a login na sua conta Vulcan usando a p\u00e1gina de registro do aplicativo m\u00f3vel." + }, + "select_saved_credentials": { + "data": { + "credentials": "Login" + }, + "description": "Selecione as credenciais salvas." + }, + "select_student": { + "data": { + "student_name": "Selecionar aluno" + }, + "description": "Selecione aluno, voc\u00ea pode adicionar mais alunos adicionando integra\u00e7\u00e3o novamente." + } + } + }, + "options": { + "error": { + "error": "Ocorreu um erro" + }, + "step": { + "init": { + "data": { + "attendance_notify": "Mostrar notifica\u00e7\u00f5es sobre as entradas de participa\u00e7\u00e3o mais recentes", + "grade_notify": "Mostrar notifica\u00e7\u00f5es sobre as notas mais recentes", + "message_notify": "Mostrar notifica\u00e7\u00f5es quando uma nova mensagem for recebida", + "scan_interval": "Intervalo de atualiza\u00e7\u00e3o (em minutos)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/ru.json b/homeassistant/components/vulcan/translations/ru.json new file mode 100644 index 00000000000..88e64d9ca79 --- /dev/null +++ b/homeassistant/components/vulcan/translations/ru.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "\u0412\u0441\u0435 \u0441\u0442\u0443\u0434\u0435\u043d\u0442\u044b \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b.", + "already_configured": "\u042d\u0442\u043e\u0442 \u0441\u0442\u0443\u0434\u0435\u043d\u0442 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "error": { + "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443.", + "expired_credentials": "\u0423\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0438\u0441\u0442\u0435\u043a\u0448\u0438\u043c \u0441\u0440\u043e\u043a\u043e\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0435 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f Vulcan.", + "expired_token": "\u0421\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0438\u0441\u0442\u0435\u043a. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u0442\u043e\u043a\u0435\u043d.", + "invalid_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434.", + "invalid_symbol": "\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0441\u0438\u043c\u0432\u043e\u043b.", + "invalid_token": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d.", + "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435" + }, + "description": "\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0435\u0449\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0442\u0443\u0434\u0435\u043d\u0442\u0430." + }, + "auth": { + "data": { + "pin": "PIN-\u043a\u043e\u0434", + "region": "\u0421\u0438\u043c\u0432\u043e\u043b", + "token": "\u0422\u043e\u043a\u0435\u043d" + }, + "description": "\u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Vulcan, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f." + }, + "reauth": { + "data": { + "pin": "PIN-\u043a\u043e\u0434", + "region": "\u0421\u0438\u043c\u0432\u043e\u043b", + "token": "\u0422\u043e\u043a\u0435\u043d" + }, + "description": "\u0412\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Vulcan, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f." + }, + "select_saved_credentials": { + "data": { + "credentials": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." + }, + "select_student": { + "data": { + "student_name": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0443\u0434\u0435\u043d\u0442\u0430" + }, + "description": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u0441\u0442\u0443\u0434\u0435\u043d\u0442\u043e\u0432, \u0441\u043d\u043e\u0432\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e." + } + } + }, + "options": { + "error": { + "error": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "init": { + "data": { + "attendance_notify": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0437\u0430\u043f\u0438\u0441\u044f\u0445 \u043e \u043f\u043e\u0441\u0435\u0449\u0430\u0435\u043c\u043e\u0441\u0442\u0438", + "grade_notify": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u043e\u0446\u0435\u043d\u043a\u0430\u0445", + "message_notify": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f", + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/tr.json b/homeassistant/components/vulcan/translations/tr.json new file mode 100644 index 00000000000..9f4324dfbcc --- /dev/null +++ b/homeassistant/components/vulcan/translations/tr.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "T\u00fcm \u00f6\u011frenciler zaten eklendi.", + "already_configured": "Bu \u00f6\u011frenci zaten eklendi.", + "reauth_successful": "Yeniden yetkilendirme ba\u015far\u0131l\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flant\u0131 hatas\u0131 - l\u00fctfen internet ba\u011flant\u0131n\u0131z\u0131 kontrol edin", + "expired_credentials": "S\u00fcresi dolmu\u015f kimlik bilgileri - l\u00fctfen Vulcan mobil uygulama kay\u0131t sayfas\u0131nda yeni olu\u015fturun", + "expired_token": "S\u00fcresi dolmu\u015f anahtar - l\u00fctfen yeni bir anahtar olu\u015fturun", + "invalid_pin": "Ge\u00e7ersiz PIN", + "invalid_symbol": "Ge\u00e7ersiz sembol", + "invalid_token": "Ge\u00e7ersiz anahtar", + "unknown": "Bilinmeyen hata olu\u015ftu" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "Kaydedilmi\u015f kimlik bilgilerini kullan" + }, + "description": "Ba\u015fka bir \u00f6\u011frenci ekleyin." + }, + "auth": { + "data": { + "pin": "Pin", + "region": "Sembol", + "token": "Anahtar" + }, + "description": "Mobil uygulama kay\u0131t sayfas\u0131n\u0131 kullanarak Vulcan Hesab\u0131n\u0131za giri\u015f yap\u0131n." + }, + "reauth": { + "data": { + "pin": "Pin", + "region": "Sembol", + "token": "Anahtar" + }, + "description": "Mobil uygulama kay\u0131t sayfas\u0131n\u0131 kullanarak Vulcan Hesab\u0131n\u0131za giri\u015f yap\u0131n." + }, + "select_saved_credentials": { + "data": { + "credentials": "Oturum a\u00e7" + }, + "description": "Kaydedilmi\u015f kimlik bilgilerini se\u00e7in." + }, + "select_student": { + "data": { + "student_name": "\u00d6\u011frenci se\u00e7" + }, + "description": "\u00d6\u011frenciyi se\u00e7in, yeniden t\u00fcmle\u015ftirme ekleyerek daha fazla \u00f6\u011frenci ekleyebilirsiniz." + } + } + }, + "options": { + "error": { + "error": "Hata olu\u015ftu" + }, + "step": { + "init": { + "data": { + "attendance_notify": "En son kat\u0131l\u0131m giri\u015fleriyle ilgili bildirimleri g\u00f6ster", + "grade_notify": "En son notlarla ilgili bildirimleri g\u00f6ster", + "message_notify": "Yeni mesaj al\u0131nd\u0131\u011f\u0131nda bildirimleri g\u00f6ster", + "scan_interval": "G\u00fcncelleme aral\u0131\u011f\u0131 (dakika olarak)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vulcan/translations/zh-Hant.json b/homeassistant/components/vulcan/translations/zh-Hant.json new file mode 100644 index 00000000000..3a1c06ce5ad --- /dev/null +++ b/homeassistant/components/vulcan/translations/zh-Hant.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "all_student_already_configured": "\u6240\u6709\u5b78\u751f\u90fd\u5df2\u7d93\u65b0\u589e\u3002", + "already_configured": "\u8a72\u5b78\u751f\u5df2\u7d93\u65b0\u589e\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u932f\u8aa4 - \u8acb\u6aa2\u5bdf\u7db2\u8def\u9023\u7dda", + "expired_credentials": "\u6191\u8b49\u904e\u671f - \u8acb\u900f\u904e Vulcan \u884c\u52d5 App \u8a3b\u518a\u9801\u9762\u91cd\u65b0\u65b0\u589e", + "expired_token": "\u6b0a\u6756\u5df2\u904e\u671f - \u8acb\u7522\u751f\u65b0\u6b0a\u6756", + "invalid_pin": "Pin \u7121\u6548", + "invalid_symbol": "\u7b26\u865f\u7121\u6548", + "invalid_token": "\u6b0a\u6756\u7121\u6548", + "unknown": "\u767c\u751f\u672a\u77e5\u932f\u8aa4" + }, + "step": { + "add_next_config_entry": { + "data": { + "use_saved_credentials": "\u4f7f\u7528\u5132\u5b58\u6191\u8b49" + }, + "description": "\u65b0\u589e\u5176\u4ed6\u5b78\u751f\u3002" + }, + "auth": { + "data": { + "pin": "Pin", + "region": "\u7b26\u865f", + "token": "\u6b0a\u6756" + }, + "description": "\u4f7f\u7528\u884c\u52d5 App \u8a3b\u518a\u9801\u9762\u767b\u5165 Vulcan \u5e33\u865f\u3002" + }, + "reauth": { + "data": { + "pin": "Pin", + "region": "\u7b26\u865f", + "token": "\u6b0a\u6756" + }, + "description": "\u4f7f\u7528\u884c\u52d5 App \u8a3b\u518a\u9801\u9762\u767b\u5165 Vulcan \u5e33\u865f\u3002" + }, + "select_saved_credentials": { + "data": { + "credentials": "\u767b\u5165" + }, + "description": "\u9078\u64c7\u5132\u5b58\u6191\u8b49\u3002" + }, + "select_student": { + "data": { + "student_name": "\u9078\u64c7\u5b78\u751f" + }, + "description": "\u9078\u64c7\u5b78\u751f\u3001\u53ef\u4ee5\u518d\u900f\u904e\u65b0\u589e\u6574\u5408\u65b0\u589e\u5176\u4ed6\u5b78\u751f\u3002" + } + } + }, + "options": { + "error": { + "error": "\u767c\u751f\u932f\u8aa4" + }, + "step": { + "init": { + "data": { + "attendance_notify": "\u95dc\u65bc\u6700\u65b0\u51fa\u52e4\u5be6\u9ad4\u986f\u793a\u901a\u77e5", + "grade_notify": "\u95dc\u65bc\u6700\u65b0\u6210\u7e3e\u986f\u793a\u901a\u77e5", + "message_notify": "\u7576\u6536\u5230\u65b0\u8a0a\u606f\u6642\u986f\u793a\u901a\u77e5", + "scan_interval": "\u66f4\u65b0\u983b\u7387\uff08\u5206\uff09" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wallbox/__init__.py b/homeassistant/components/wallbox/__init__.py index f9510421ba0..332a1ee6741 100644 --- a/homeassistant/components/wallbox/__init__.py +++ b/homeassistant/components/wallbox/__init__.py @@ -20,56 +20,57 @@ from homeassistant.helpers.update_coordinator import ( from ...helpers.entity import DeviceInfo from .const import ( - CONF_CURRENT_VERSION_KEY, - CONF_DATA_KEY, - CONF_LOCKED_UNLOCKED_KEY, - CONF_MAX_CHARGING_CURRENT_KEY, - CONF_NAME_KEY, - CONF_PART_NUMBER_KEY, - CONF_SERIAL_NUMBER_KEY, - CONF_SOFTWARE_KEY, + CHARGER_CURRENT_VERSION_KEY, + CHARGER_DATA_KEY, + CHARGER_LOCKED_UNLOCKED_KEY, + CHARGER_MAX_CHARGING_CURRENT_KEY, + CHARGER_NAME_KEY, + CHARGER_PART_NUMBER_KEY, + CHARGER_SERIAL_NUMBER_KEY, + CHARGER_SOFTWARE_KEY, + CHARGER_STATUS_DESCRIPTION_KEY, + CHARGER_STATUS_ID_KEY, CONF_STATION, - CONF_STATUS_DESCRIPTION_KEY, - CONF_STATUS_ID_KEY, DOMAIN, + ChargerStatus, ) _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.LOCK] +PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.LOCK, Platform.SWITCH] UPDATE_INTERVAL = 30 # Translation of StatusId based on Wallbox portal code: # https://my.wallbox.com/src/utilities/charger/chargerStatuses.js -CHARGER_STATUS: dict[int, str] = { - 0: "Disconnected", - 14: "Error", - 15: "Error", - 161: "Ready", - 162: "Ready", - 163: "Disconnected", - 164: "Waiting", - 165: "Locked", - 166: "Updating", - 177: "Scheduled", - 178: "Paused", - 179: "Scheduled", - 180: "Waiting for car demand", - 181: "Waiting for car demand", - 182: "Paused", - 183: "Waiting in queue by Power Sharing", - 184: "Waiting in queue by Power Sharing", - 185: "Waiting in queue by Power Boost", - 186: "Waiting in queue by Power Boost", - 187: "Waiting MID failed", - 188: "Waiting MID safety margin exceeded", - 189: "Waiting in queue by Eco-Smart", - 193: "Charging", - 194: "Charging", - 195: "Charging", - 196: "Discharging", - 209: "Locked", - 210: "Locked", +CHARGER_STATUS: dict[int, ChargerStatus] = { + 0: ChargerStatus.DISCONNECTED, + 14: ChargerStatus.ERROR, + 15: ChargerStatus.ERROR, + 161: ChargerStatus.READY, + 162: ChargerStatus.READY, + 163: ChargerStatus.DISCONNECTED, + 164: ChargerStatus.WAITING, + 165: ChargerStatus.LOCKED, + 166: ChargerStatus.UPDATING, + 177: ChargerStatus.SCHEDULED, + 178: ChargerStatus.PAUSED, + 179: ChargerStatus.SCHEDULED, + 180: ChargerStatus.WAITING_FOR_CAR, + 181: ChargerStatus.WAITING_FOR_CAR, + 182: ChargerStatus.PAUSED, + 183: ChargerStatus.WAITING_IN_QUEUE_POWER_SHARING, + 184: ChargerStatus.WAITING_IN_QUEUE_POWER_SHARING, + 185: ChargerStatus.WAITING_IN_QUEUE_POWER_BOOST, + 186: ChargerStatus.WAITING_IN_QUEUE_POWER_BOOST, + 187: ChargerStatus.WAITING_MID_FAILED, + 188: ChargerStatus.WAITING_MID_SAFETY, + 189: ChargerStatus.WAITING_IN_QUEUE_ECO_SMART, + 193: ChargerStatus.CHARGING, + 194: ChargerStatus.CHARGING, + 195: ChargerStatus.CHARGING, + 196: ChargerStatus.DISCHARGING, + 209: ChargerStatus.LOCKED, + 210: ChargerStatus.LOCKED, } @@ -115,14 +116,14 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): try: self._authenticate() data: dict[str, Any] = self._wallbox.getChargerStatus(self._station) - data[CONF_MAX_CHARGING_CURRENT_KEY] = data[CONF_DATA_KEY][ - CONF_MAX_CHARGING_CURRENT_KEY + data[CHARGER_MAX_CHARGING_CURRENT_KEY] = data[CHARGER_DATA_KEY][ + CHARGER_MAX_CHARGING_CURRENT_KEY ] - data[CONF_LOCKED_UNLOCKED_KEY] = data[CONF_DATA_KEY][ - CONF_LOCKED_UNLOCKED_KEY + data[CHARGER_LOCKED_UNLOCKED_KEY] = data[CHARGER_DATA_KEY][ + CHARGER_LOCKED_UNLOCKED_KEY ] - data[CONF_STATUS_DESCRIPTION_KEY] = CHARGER_STATUS.get( - data[CONF_STATUS_ID_KEY], "Unknown" + data[CHARGER_STATUS_DESCRIPTION_KEY] = CHARGER_STATUS.get( + data[CHARGER_STATUS_ID_KEY], ChargerStatus.UNKNOWN ) return data @@ -169,6 +170,24 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): await self.hass.async_add_executor_job(self._set_lock_unlock, lock) await self.async_request_refresh() + def _pause_charger(self, pause: bool) -> None: + """Set wallbox to pause or resume.""" + try: + self._authenticate() + if pause: + self._wallbox.pauseChargingSession(self._station) + else: + self._wallbox.resumeChargingSession(self._station) + except requests.exceptions.HTTPError as wallbox_connection_error: + if wallbox_connection_error.response.status_code == 403: + raise InvalidAuth from wallbox_connection_error + raise ConnectionError from wallbox_connection_error + + async def async_pause_charger(self, pause: bool) -> None: + """Set wallbox to pause or resume.""" + await self.hass.async_add_executor_job(self._pause_charger, pause) + await self.async_request_refresh() + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Wallbox from a config entry.""" @@ -215,12 +234,15 @@ class WallboxEntity(CoordinatorEntity[WallboxCoordinator]): """Return device information about this Wallbox device.""" return DeviceInfo( identifiers={ - (DOMAIN, self.coordinator.data[CONF_DATA_KEY][CONF_SERIAL_NUMBER_KEY]) + ( + DOMAIN, + self.coordinator.data[CHARGER_DATA_KEY][CHARGER_SERIAL_NUMBER_KEY], + ) }, - name=f"Wallbox - {self.coordinator.data[CONF_NAME_KEY]}", + name=f"Wallbox - {self.coordinator.data[CHARGER_NAME_KEY]}", manufacturer="Wallbox", - model=self.coordinator.data[CONF_DATA_KEY][CONF_PART_NUMBER_KEY], - sw_version=self.coordinator.data[CONF_DATA_KEY][CONF_SOFTWARE_KEY][ - CONF_CURRENT_VERSION_KEY + model=self.coordinator.data[CHARGER_DATA_KEY][CHARGER_PART_NUMBER_KEY], + sw_version=self.coordinator.data[CHARGER_DATA_KEY][CHARGER_SOFTWARE_KEY][ + CHARGER_CURRENT_VERSION_KEY ], ) diff --git a/homeassistant/components/wallbox/const.py b/homeassistant/components/wallbox/const.py index e4b33f6bc6b..6152207427b 100644 --- a/homeassistant/components/wallbox/const.py +++ b/homeassistant/components/wallbox/const.py @@ -1,26 +1,50 @@ """Constants for the Wallbox integration.""" +from homeassistant.backports.enum import StrEnum DOMAIN = "wallbox" CONF_STATION = "station" -CONF_ADDED_ENERGY_KEY = "added_energy" -CONF_ADDED_RANGE_KEY = "added_range" -CONF_CHARGING_POWER_KEY = "charging_power" -CONF_CHARGING_SPEED_KEY = "charging_speed" -CONF_CHARGING_TIME_KEY = "charging_time" -CONF_COST_KEY = "cost" -CONF_CURRENT_MODE_KEY = "current_mode" -CONF_CURRENT_VERSION_KEY = "currentVersion" -CONF_DATA_KEY = "config_data" -CONF_DEPOT_PRICE_KEY = "depot_price" -CONF_SERIAL_NUMBER_KEY = "serial_number" -CONF_PART_NUMBER_KEY = "part_number" -CONF_SOFTWARE_KEY = "software" -CONF_MAX_AVAILABLE_POWER_KEY = "max_available_power" -CONF_MAX_CHARGING_CURRENT_KEY = "max_charging_current" -CONF_LOCKED_UNLOCKED_KEY = "locked" -CONF_NAME_KEY = "name" -CONF_STATE_OF_CHARGE_KEY = "state_of_charge" -CONF_STATUS_ID_KEY = "status_id" -CONF_STATUS_DESCRIPTION_KEY = "status_description" -CONF_CONNECTIONS = "connections" +CHARGER_ADDED_ENERGY_KEY = "added_energy" +CHARGER_ADDED_RANGE_KEY = "added_range" +CHARGER_CHARGING_POWER_KEY = "charging_power" +CHARGER_CHARGING_SPEED_KEY = "charging_speed" +CHARGER_CHARGING_TIME_KEY = "charging_time" +CHARGER_COST_KEY = "cost" +CHARGER_CURRENT_MODE_KEY = "current_mode" +CHARGER_CURRENT_VERSION_KEY = "currentVersion" +CHARGER_DATA_KEY = "config_data" +CHARGER_DEPOT_PRICE_KEY = "depot_price" +CHARGER_SERIAL_NUMBER_KEY = "serial_number" +CHARGER_PART_NUMBER_KEY = "part_number" +CHARGER_SOFTWARE_KEY = "software" +CHARGER_MAX_AVAILABLE_POWER_KEY = "max_available_power" +CHARGER_MAX_CHARGING_CURRENT_KEY = "max_charging_current" +CHARGER_PAUSE_RESUME_KEY = "paused" +CHARGER_LOCKED_UNLOCKED_KEY = "locked" +CHARGER_NAME_KEY = "name" +CHARGER_STATE_OF_CHARGE_KEY = "state_of_charge" +CHARGER_STATUS_ID_KEY = "status_id" +CHARGER_STATUS_DESCRIPTION_KEY = "status_description" +CHARGER_CONNECTIONS = "connections" + + +class ChargerStatus(StrEnum): + """Charger Status Description.""" + + CHARGING = "Charging" + DISCHARGING = "Discharging" + PAUSED = "Paused" + SCHEDULED = "Scheduled" + WAITING_FOR_CAR = "Waiting for car demand" + WAITING = "Waiting" + DISCONNECTED = "Disconnected" + ERROR = "Error" + READY = "Ready" + LOCKED = "Locked" + UPDATING = "Updating" + WAITING_IN_QUEUE_POWER_SHARING = "Waiting in queue by Power Sharing" + WAITING_IN_QUEUE_POWER_BOOST = "Waiting in queue by Power Boost" + WAITING_MID_FAILED = "Waiting MID failed" + WAITING_MID_SAFETY = "Waiting MID safety margin exceeded" + WAITING_IN_QUEUE_ECO_SMART = "Waiting in queue by Eco-Smart" + UNKNOWN = "Unknown" diff --git a/homeassistant/components/wallbox/lock.py b/homeassistant/components/wallbox/lock.py index 1d12f086abe..d1b3c787735 100644 --- a/homeassistant/components/wallbox/lock.py +++ b/homeassistant/components/wallbox/lock.py @@ -10,15 +10,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import InvalidAuth, WallboxCoordinator, WallboxEntity from .const import ( - CONF_DATA_KEY, - CONF_LOCKED_UNLOCKED_KEY, - CONF_SERIAL_NUMBER_KEY, + CHARGER_DATA_KEY, + CHARGER_LOCKED_UNLOCKED_KEY, + CHARGER_SERIAL_NUMBER_KEY, DOMAIN, ) LOCK_TYPES: dict[str, LockEntityDescription] = { - CONF_LOCKED_UNLOCKED_KEY: LockEntityDescription( - key=CONF_LOCKED_UNLOCKED_KEY, + CHARGER_LOCKED_UNLOCKED_KEY: LockEntityDescription( + key=CHARGER_LOCKED_UNLOCKED_KEY, name="Locked/Unlocked", ), } @@ -32,7 +32,7 @@ async def async_setup_entry( # Check if the user is authorized to lock, if so, add lock component try: await coordinator.async_set_lock_unlock( - coordinator.data[CONF_LOCKED_UNLOCKED_KEY] + coordinator.data[CHARGER_LOCKED_UNLOCKED_KEY] ) except InvalidAuth: return @@ -60,12 +60,12 @@ class WallboxLock(WallboxEntity, LockEntity): super().__init__(coordinator) self.entity_description = description self._attr_name = f"{entry.title} {description.name}" - self._attr_unique_id = f"{description.key}-{coordinator.data[CONF_DATA_KEY][CONF_SERIAL_NUMBER_KEY]}" + self._attr_unique_id = f"{description.key}-{coordinator.data[CHARGER_DATA_KEY][CHARGER_SERIAL_NUMBER_KEY]}" @property def is_locked(self) -> bool: """Return the status of the lock.""" - return self.coordinator.data[CONF_LOCKED_UNLOCKED_KEY] # type: ignore[no-any-return] + return self.coordinator.data[CHARGER_LOCKED_UNLOCKED_KEY] # type: ignore[no-any-return] async def async_lock(self, **kwargs: Any) -> None: """Lock charger.""" diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index 5822e7d45d9..1ad06145ed5 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -11,10 +11,10 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import InvalidAuth, WallboxCoordinator, WallboxEntity from .const import ( - CONF_DATA_KEY, - CONF_MAX_AVAILABLE_POWER_KEY, - CONF_MAX_CHARGING_CURRENT_KEY, - CONF_SERIAL_NUMBER_KEY, + CHARGER_DATA_KEY, + CHARGER_MAX_AVAILABLE_POWER_KEY, + CHARGER_MAX_CHARGING_CURRENT_KEY, + CHARGER_SERIAL_NUMBER_KEY, DOMAIN, ) @@ -25,8 +25,8 @@ class WallboxNumberEntityDescription(NumberEntityDescription): NUMBER_TYPES: dict[str, WallboxNumberEntityDescription] = { - CONF_MAX_CHARGING_CURRENT_KEY: WallboxNumberEntityDescription( - key=CONF_MAX_CHARGING_CURRENT_KEY, + CHARGER_MAX_CHARGING_CURRENT_KEY: WallboxNumberEntityDescription( + key=CHARGER_MAX_CHARGING_CURRENT_KEY, name="Max. Charging Current", min_value=6, ), @@ -41,7 +41,7 @@ async def async_setup_entry( # Check if the user is authorized to change current, if so, add number component: try: await coordinator.async_set_charging_current( - coordinator.data[CONF_MAX_CHARGING_CURRENT_KEY] + coordinator.data[CHARGER_MAX_CHARGING_CURRENT_KEY] ) except InvalidAuth: return @@ -71,18 +71,18 @@ class WallboxNumber(WallboxEntity, NumberEntity): self.entity_description = description self._coordinator = coordinator self._attr_name = f"{entry.title} {description.name}" - self._attr_unique_id = f"{description.key}-{coordinator.data[CONF_DATA_KEY][CONF_SERIAL_NUMBER_KEY]}" + self._attr_unique_id = f"{description.key}-{coordinator.data[CHARGER_DATA_KEY][CHARGER_SERIAL_NUMBER_KEY]}" @property def max_value(self) -> float: """Return the maximum available current.""" - return cast(float, self._coordinator.data[CONF_MAX_AVAILABLE_POWER_KEY]) + return cast(float, self._coordinator.data[CHARGER_MAX_AVAILABLE_POWER_KEY]) @property def value(self) -> float | None: """Return the state of the sensor.""" return cast( - Optional[float], self._coordinator.data[CONF_MAX_CHARGING_CURRENT_KEY] + Optional[float], self._coordinator.data[CHARGER_MAX_CHARGING_CURRENT_KEY] ) async def async_set_value(self, value: float) -> None: diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index 9a7fd1a21a3..c8ad3cb3a67 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -25,23 +25,23 @@ from homeassistant.helpers.typing import StateType from . import WallboxCoordinator, WallboxEntity from .const import ( - CONF_ADDED_ENERGY_KEY, - CONF_ADDED_RANGE_KEY, - CONF_CHARGING_POWER_KEY, - CONF_CHARGING_SPEED_KEY, - CONF_COST_KEY, - CONF_CURRENT_MODE_KEY, - CONF_DATA_KEY, - CONF_DEPOT_PRICE_KEY, - CONF_MAX_AVAILABLE_POWER_KEY, - CONF_MAX_CHARGING_CURRENT_KEY, - CONF_SERIAL_NUMBER_KEY, - CONF_STATE_OF_CHARGE_KEY, - CONF_STATUS_DESCRIPTION_KEY, + CHARGER_ADDED_ENERGY_KEY, + CHARGER_ADDED_RANGE_KEY, + CHARGER_CHARGING_POWER_KEY, + CHARGER_CHARGING_SPEED_KEY, + CHARGER_COST_KEY, + CHARGER_CURRENT_MODE_KEY, + CHARGER_DATA_KEY, + CHARGER_DEPOT_PRICE_KEY, + CHARGER_MAX_AVAILABLE_POWER_KEY, + CHARGER_MAX_CHARGING_CURRENT_KEY, + CHARGER_SERIAL_NUMBER_KEY, + CHARGER_STATE_OF_CHARGE_KEY, + CHARGER_STATUS_DESCRIPTION_KEY, DOMAIN, ) -CONF_STATION = "station" +CHARGER_STATION = "station" UPDATE_INTERVAL = 30 _LOGGER = logging.getLogger(__name__) @@ -55,76 +55,76 @@ class WallboxSensorEntityDescription(SensorEntityDescription): SENSOR_TYPES: dict[str, WallboxSensorEntityDescription] = { - CONF_CHARGING_POWER_KEY: WallboxSensorEntityDescription( - key=CONF_CHARGING_POWER_KEY, + CHARGER_CHARGING_POWER_KEY: WallboxSensorEntityDescription( + key=CHARGER_CHARGING_POWER_KEY, name="Charging Power", precision=2, native_unit_of_measurement=POWER_KILO_WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), - CONF_MAX_AVAILABLE_POWER_KEY: WallboxSensorEntityDescription( - key=CONF_MAX_AVAILABLE_POWER_KEY, + CHARGER_MAX_AVAILABLE_POWER_KEY: WallboxSensorEntityDescription( + key=CHARGER_MAX_AVAILABLE_POWER_KEY, name="Max Available Power", precision=0, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, ), - CONF_CHARGING_SPEED_KEY: WallboxSensorEntityDescription( - key=CONF_CHARGING_SPEED_KEY, + CHARGER_CHARGING_SPEED_KEY: WallboxSensorEntityDescription( + key=CHARGER_CHARGING_SPEED_KEY, icon="mdi:speedometer", name="Charging Speed", precision=0, state_class=SensorStateClass.MEASUREMENT, ), - CONF_ADDED_RANGE_KEY: WallboxSensorEntityDescription( - key=CONF_ADDED_RANGE_KEY, + CHARGER_ADDED_RANGE_KEY: WallboxSensorEntityDescription( + key=CHARGER_ADDED_RANGE_KEY, icon="mdi:map-marker-distance", name="Added Range", precision=0, native_unit_of_measurement=LENGTH_KILOMETERS, state_class=SensorStateClass.TOTAL_INCREASING, ), - CONF_ADDED_ENERGY_KEY: WallboxSensorEntityDescription( - key=CONF_ADDED_ENERGY_KEY, + CHARGER_ADDED_ENERGY_KEY: WallboxSensorEntityDescription( + key=CHARGER_ADDED_ENERGY_KEY, name="Added Energy", precision=2, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), - CONF_COST_KEY: WallboxSensorEntityDescription( - key=CONF_COST_KEY, + CHARGER_COST_KEY: WallboxSensorEntityDescription( + key=CHARGER_COST_KEY, icon="mdi:ev-station", name="Cost", state_class=SensorStateClass.TOTAL_INCREASING, ), - CONF_STATE_OF_CHARGE_KEY: WallboxSensorEntityDescription( - key=CONF_STATE_OF_CHARGE_KEY, + CHARGER_STATE_OF_CHARGE_KEY: WallboxSensorEntityDescription( + key=CHARGER_STATE_OF_CHARGE_KEY, name="State of Charge", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, ), - CONF_CURRENT_MODE_KEY: WallboxSensorEntityDescription( - key=CONF_CURRENT_MODE_KEY, + CHARGER_CURRENT_MODE_KEY: WallboxSensorEntityDescription( + key=CHARGER_CURRENT_MODE_KEY, icon="mdi:ev-station", name="Current Mode", ), - CONF_DEPOT_PRICE_KEY: WallboxSensorEntityDescription( - key=CONF_DEPOT_PRICE_KEY, + CHARGER_DEPOT_PRICE_KEY: WallboxSensorEntityDescription( + key=CHARGER_DEPOT_PRICE_KEY, icon="mdi:ev-station", name="Depot Price", precision=2, ), - CONF_STATUS_DESCRIPTION_KEY: WallboxSensorEntityDescription( - key=CONF_STATUS_DESCRIPTION_KEY, + CHARGER_STATUS_DESCRIPTION_KEY: WallboxSensorEntityDescription( + key=CHARGER_STATUS_DESCRIPTION_KEY, icon="mdi:ev-station", name="Status Description", ), - CONF_MAX_CHARGING_CURRENT_KEY: WallboxSensorEntityDescription( - key=CONF_MAX_CHARGING_CURRENT_KEY, + CHARGER_MAX_CHARGING_CURRENT_KEY: WallboxSensorEntityDescription( + key=CHARGER_MAX_CHARGING_CURRENT_KEY, name="Max. Charging Current", native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, device_class=SensorDeviceClass.CURRENT, @@ -163,7 +163,7 @@ class WallboxSensor(WallboxEntity, SensorEntity): super().__init__(coordinator) self.entity_description = description self._attr_name = f"{entry.title} {description.name}" - self._attr_unique_id = f"{description.key}-{coordinator.data[CONF_DATA_KEY][CONF_SERIAL_NUMBER_KEY]}" + self._attr_unique_id = f"{description.key}-{coordinator.data[CHARGER_DATA_KEY][CHARGER_SERIAL_NUMBER_KEY]}" @property def native_value(self) -> StateType: diff --git a/homeassistant/components/wallbox/switch.py b/homeassistant/components/wallbox/switch.py new file mode 100644 index 00000000000..7ef6f1e97ed --- /dev/null +++ b/homeassistant/components/wallbox/switch.py @@ -0,0 +1,78 @@ +"""Home Assistant component for accessing the Wallbox Portal API. The switch component creates a switch entity.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import WallboxCoordinator, WallboxEntity +from .const import ( + CHARGER_DATA_KEY, + CHARGER_PAUSE_RESUME_KEY, + CHARGER_SERIAL_NUMBER_KEY, + CHARGER_STATUS_DESCRIPTION_KEY, + DOMAIN, + ChargerStatus, +) + +SWITCH_TYPES: dict[str, SwitchEntityDescription] = { + CHARGER_PAUSE_RESUME_KEY: SwitchEntityDescription( + key=CHARGER_PAUSE_RESUME_KEY, + name="Pause/Resume", + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Create wallbox sensor entities in HASS.""" + coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + [WallboxSwitch(coordinator, entry, SWITCH_TYPES[CHARGER_PAUSE_RESUME_KEY])] + ) + + +class WallboxSwitch(WallboxEntity, SwitchEntity): + """Representation of the Wallbox portal.""" + + def __init__( + self, + coordinator: WallboxCoordinator, + entry: ConfigEntry, + description: SwitchEntityDescription, + ) -> None: + """Initialize a Wallbox switch.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_name = f"{entry.title} {description.name}" + self._attr_unique_id = f"{description.key}-{coordinator.data[CHARGER_DATA_KEY][CHARGER_SERIAL_NUMBER_KEY]}" + + @property + def available(self) -> bool: + """Return the availability of the switch.""" + return self.coordinator.data[CHARGER_STATUS_DESCRIPTION_KEY] in { + ChargerStatus.CHARGING, + ChargerStatus.PAUSED, + ChargerStatus.SCHEDULED, + } + + @property + def is_on(self) -> bool: + """Return the status of pause/resume.""" + return self.coordinator.data[CHARGER_STATUS_DESCRIPTION_KEY] in { + ChargerStatus.CHARGING, + ChargerStatus.WAITING_FOR_CAR, + ChargerStatus.WAITING, + } + + async def async_turn_off(self, **kwargs: Any) -> None: + """Pause charger.""" + await self.coordinator.async_pause_charger(True) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Resume charger.""" + await self.coordinator.async_pause_charger(False) diff --git a/homeassistant/components/wallbox/translations/bg.json b/homeassistant/components/wallbox/translations/bg.json index 25f3bb50845..cc0ea3ece2d 100644 --- a/homeassistant/components/wallbox/translations/bg.json +++ b/homeassistant/components/wallbox/translations/bg.json @@ -24,6 +24,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/ca.json b/homeassistant/components/wallbox/translations/ca.json index b6a10e16e2a..6d76243a479 100644 --- a/homeassistant/components/wallbox/translations/ca.json +++ b/homeassistant/components/wallbox/translations/ca.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/de.json b/homeassistant/components/wallbox/translations/de.json index d415b4f9e7a..2aa206d1b7e 100644 --- a/homeassistant/components/wallbox/translations/de.json +++ b/homeassistant/components/wallbox/translations/de.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/el.json b/homeassistant/components/wallbox/translations/el.json index dd95266866f..48443b1b45f 100644 --- a/homeassistant/components/wallbox/translations/el.json +++ b/homeassistant/components/wallbox/translations/el.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/en.json b/homeassistant/components/wallbox/translations/en.json index 28ec5d08235..f32c7b7b481 100644 --- a/homeassistant/components/wallbox/translations/en.json +++ b/homeassistant/components/wallbox/translations/en.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/es.json b/homeassistant/components/wallbox/translations/es.json index 72c7b0587d6..1c5315d6745 100644 --- a/homeassistant/components/wallbox/translations/es.json +++ b/homeassistant/components/wallbox/translations/es.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/et.json b/homeassistant/components/wallbox/translations/et.json index f5d3b3aac73..cfe0720616a 100644 --- a/homeassistant/components/wallbox/translations/et.json +++ b/homeassistant/components/wallbox/translations/et.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/fr.json b/homeassistant/components/wallbox/translations/fr.json index b9499f72b15..e918573e13b 100644 --- a/homeassistant/components/wallbox/translations/fr.json +++ b/homeassistant/components/wallbox/translations/fr.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/he.json b/homeassistant/components/wallbox/translations/he.json index 6109bb22195..06a2f86efa9 100644 --- a/homeassistant/components/wallbox/translations/he.json +++ b/homeassistant/components/wallbox/translations/he.json @@ -23,6 +23,5 @@ } } } - }, - "title": "\u05ea\u05d9\u05d1\u05ea \u05e7\u05d9\u05e8" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/hu.json b/homeassistant/components/wallbox/translations/hu.json index 5579da0fe88..d6d0c4837d4 100644 --- a/homeassistant/components/wallbox/translations/hu.json +++ b/homeassistant/components/wallbox/translations/hu.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/id.json b/homeassistant/components/wallbox/translations/id.json index 08611ab3c2e..430a0eac114 100644 --- a/homeassistant/components/wallbox/translations/id.json +++ b/homeassistant/components/wallbox/translations/id.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/it.json b/homeassistant/components/wallbox/translations/it.json index ce4bdef9d95..726a77396e2 100644 --- a/homeassistant/components/wallbox/translations/it.json +++ b/homeassistant/components/wallbox/translations/it.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/ja.json b/homeassistant/components/wallbox/translations/ja.json index 8924bf891b9..4aa79afcb22 100644 --- a/homeassistant/components/wallbox/translations/ja.json +++ b/homeassistant/components/wallbox/translations/ja.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/nl.json b/homeassistant/components/wallbox/translations/nl.json index dbe8bd91f72..5cde7830b85 100644 --- a/homeassistant/components/wallbox/translations/nl.json +++ b/homeassistant/components/wallbox/translations/nl.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/no.json b/homeassistant/components/wallbox/translations/no.json index 74bbb1f39d5..498362fad1d 100644 --- a/homeassistant/components/wallbox/translations/no.json +++ b/homeassistant/components/wallbox/translations/no.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/pl.json b/homeassistant/components/wallbox/translations/pl.json index 51180d4c68e..369049c3812 100644 --- a/homeassistant/components/wallbox/translations/pl.json +++ b/homeassistant/components/wallbox/translations/pl.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/pt-BR.json b/homeassistant/components/wallbox/translations/pt-BR.json index 3fb6428603a..ea7b6c899ed 100644 --- a/homeassistant/components/wallbox/translations/pt-BR.json +++ b/homeassistant/components/wallbox/translations/pt-BR.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/ru.json b/homeassistant/components/wallbox/translations/ru.json index 426c07c9423..e35de176bf9 100644 --- a/homeassistant/components/wallbox/translations/ru.json +++ b/homeassistant/components/wallbox/translations/ru.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/tr.json b/homeassistant/components/wallbox/translations/tr.json index 1bee24a696d..a1c69aea28d 100644 --- a/homeassistant/components/wallbox/translations/tr.json +++ b/homeassistant/components/wallbox/translations/tr.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/wallbox/translations/zh-Hant.json b/homeassistant/components/wallbox/translations/zh-Hant.json index 3f282cda1f9..2688a050ce0 100644 --- a/homeassistant/components/wallbox/translations/zh-Hant.json +++ b/homeassistant/components/wallbox/translations/zh-Hant.json @@ -25,6 +25,5 @@ } } } - }, - "title": "Wallbox" + } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 57358f3d601..e24d117f678 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta +from enum import IntEnum import functools as ft import logging from typing import final @@ -55,6 +56,17 @@ STATE_HIGH_DEMAND = "high_demand" STATE_HEAT_PUMP = "heat_pump" STATE_GAS = "gas" + +class WaterHeaterEntityFeature(IntEnum): + """Supported features of the fan entity.""" + + TARGET_TEMPERATURE = 1 + OPERATION_MODE = 2 + AWAY_MODE = 4 + + +# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. +# Please use the WaterHeaterEntityFeature enum instead. SUPPORT_TARGET_TEMPERATURE = 1 SUPPORT_OPERATION_MODE = 2 SUPPORT_AWAY_MODE = 4 @@ -189,7 +201,7 @@ class WaterHeaterEntity(Entity): ), } - if supported_features & SUPPORT_OPERATION_MODE: + if supported_features & WaterHeaterEntityFeature.OPERATION_MODE: data[ATTR_OPERATION_LIST] = self.operation_list return data @@ -227,10 +239,10 @@ class WaterHeaterEntity(Entity): supported_features = self.supported_features - if supported_features & SUPPORT_OPERATION_MODE: + if supported_features & WaterHeaterEntityFeature.OPERATION_MODE: data[ATTR_OPERATION_MODE] = self.current_operation - if supported_features & SUPPORT_AWAY_MODE: + if supported_features & WaterHeaterEntityFeature.AWAY_MODE: is_away = self.is_away_mode_on data[ATTR_AWAY_MODE] = STATE_ON if is_away else STATE_OFF diff --git a/homeassistant/components/waze_travel_time/translations/hu.json b/homeassistant/components/waze_travel_time/translations/hu.json index 401eb3c814c..75bc311d814 100644 --- a/homeassistant/components/waze_travel_time/translations/hu.json +++ b/homeassistant/components/waze_travel_time/translations/hu.json @@ -10,7 +10,7 @@ "user": { "data": { "destination": "\u00c9rkez\u00e9s helye", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "origin": "Indul\u00e1s helye", "region": "R\u00e9gi\u00f3" }, diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 95233cca9ca..798f863b8ee 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -145,7 +145,6 @@ class WebhookView(HomeAssistantView): async def _handle(self, request: Request, webhook_id: str) -> Response: """Handle webhook call.""" - # pylint: disable=no-self-use _LOGGER.debug("Handling webhook %s payload for %s", request.method, webhook_id) hass = request.app["hass"] return await async_handle_webhook(hass, webhook_id, request) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 67125c45ef5..49f9a29052b 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -15,22 +15,9 @@ from homeassistant import util from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntity, + MediaPlayerEntityFeature, ) -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, -) +from homeassistant.components.media_player.const import MEDIA_TYPE_CHANNEL from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, @@ -61,17 +48,19 @@ from .const import ( _LOGGER = logging.getLogger(__name__) SUPPORT_WEBOSTV = ( - SUPPORT_TURN_OFF - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY_MEDIA - | SUPPORT_PLAY - | SUPPORT_STOP + MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY_MEDIA + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.STOP ) -SUPPORT_WEBOSTV_VOLUME = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP +SUPPORT_WEBOSTV_VOLUME = ( + MediaPlayerEntityFeature.VOLUME_MUTE | MediaPlayerEntityFeature.VOLUME_STEP +) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) @@ -169,7 +158,8 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): and (state := await self.async_get_last_state()) is not None ): self._supported_features = ( - state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & ~SUPPORT_TURN_ON + state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & ~MediaPlayerEntityFeature.TURN_ON ) async def async_will_remove_from_hass(self) -> None: @@ -232,7 +222,11 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): if self._client.sound_output in ("external_arc", "external_speaker"): supported = supported | SUPPORT_WEBOSTV_VOLUME elif self._client.sound_output != "lineout": - supported = supported | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET + supported = ( + supported + | SUPPORT_WEBOSTV_VOLUME + | MediaPlayerEntityFeature.VOLUME_SET + ) self._supported_features = supported @@ -322,7 +316,7 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): def supported_features(self) -> int: """Flag media player features that are supported.""" if self._wrapper.turn_on: - return self._supported_features | SUPPORT_TURN_ON + return self._supported_features | MediaPlayerEntityFeature.TURN_ON return self._supported_features diff --git a/homeassistant/components/webostv/translations/hu.json b/homeassistant/components/webostv/translations/hu.json new file mode 100644 index 00000000000..de3f59d8c90 --- /dev/null +++ b/homeassistant/components/webostv/translations/hu.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", + "error_pairing": "Csatlakozva az LG webOS TV-hez, de a p\u00e1ros\u00edt\u00e1s nem siker\u00fclt" + }, + "error": { + "cannot_connect": "Nem siker\u00fclt csatlakozni, k\u00e9rj\u00fck, kapcsolja be a TV-t vagy ellen\u0151rizze az ip-c\u00edmet." + }, + "flow_title": "LG webOS Smart TV", + "step": { + "pairing": { + "description": "K\u00fcldje el a k\u00e9r\u00e9st, \u00e9s fogadja el a p\u00e1ros\u00edt\u00e1si k\u00e9relmet a t\u00e9v\u00e9n. \n\n ![Image](/static/images/config_webos.png)", + "title": "webOS TV p\u00e1ros\u00edt\u00e1s" + }, + "user": { + "data": { + "host": "C\u00edm", + "name": "Elnevez\u00e9s" + }, + "description": "Kapcsolja be a TV-t, t\u00f6ltse ki a k\u00f6vetkez\u0151 mez\u0151ket, ut\u00e1na folytassa", + "title": "Csatlakoz\u00e1s a webOS TV-hez" + } + } + }, + "device_automation": { + "trigger_type": { + "webostv.turn_on": "Az eszk\u00f6znek be kell lennie kapcsolva" + } + }, + "options": { + "error": { + "cannot_retrieve": "Nem siker\u00fclt lek\u00e9rni a forr\u00e1sok list\u00e1j\u00e1t. Gy\u0151z\u0151dj\u00f6n meg r\u00f3la, hogy a k\u00e9sz\u00fcl\u00e9k be van kapcsolva", + "script_not_found": "A szkript nem tal\u00e1lhat\u00f3" + }, + "step": { + "init": { + "data": { + "sources": "Forr\u00e1sok list\u00e1ja" + }, + "description": "Enged\u00e9lyezett forr\u00e1sok kiv\u00e1laszt\u00e1sa", + "title": "A webOS Smart TV be\u00e1ll\u00edt\u00e1sai" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 02121845ad6..414913be002 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -12,7 +12,6 @@ import voluptuous as vol from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_READ from homeassistant.const import ( EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, MATCH_ALL, SIGNAL_BOOTSTRAP_INTEGRATONS, ) @@ -113,9 +112,6 @@ def handle_subscribe_events( @callback def forward_events(event: Event) -> None: """Forward events to websocket.""" - if event.event_type == EVENT_TIME_CHANGED: - return - connection.send_message(messages.cached_event_message(msg["id"], event)) connection.subscriptions[msg["id"]] = hass.bus.async_listen( diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index b657b5f5d94..44b2aa8579c 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -42,7 +42,6 @@ class WebsocketAPIView(HomeAssistantView): async def get(self, request: web.Request) -> web.WebSocketResponse: """Handle an incoming websocket connection.""" - # pylint: disable=no-self-use return await WebSocketHandler(request.app["hass"], request).async_handle() diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 253ff34213e..d62a7a3b7e3 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -9,7 +9,7 @@ from typing import Any from pywemo.ouimeaux_device.humidifier import DesiredHumidity, FanMode, Humidifier import voluptuous as vol -from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_platform @@ -41,9 +41,6 @@ ATTR_WATER_LEVEL = "water_level" SPEED_RANGE = (FanMode.Minimum, FanMode.Maximum) # off is not included -SUPPORTED_FEATURES = SUPPORT_SET_SPEED - - SET_HUMIDITY_SCHEMA = { vol.Required(ATTR_TARGET_HUMIDITY): vol.All( vol.Coerce(float), vol.Range(min=0, max=100) @@ -87,6 +84,7 @@ async def async_setup_entry( class WemoHumidifier(WemoBinaryStateEntity, FanEntity): """Representation of a WeMo humidifier.""" + _attr_supported_features = FanEntityFeature.SET_SPEED wemo: Humidifier def __init__(self, coordinator: DeviceCoordinator) -> None: @@ -124,11 +122,6 @@ class WemoHumidifier(WemoBinaryStateEntity, FanEntity): """Return the number of speeds the fan supports.""" return int_states_in_range(SPEED_RANGE) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORTED_FEATURES - @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" diff --git a/homeassistant/components/whirlpool/climate.py b/homeassistant/components/whirlpool/climate.py index 40d3e80d353..c3786fd5f55 100644 --- a/homeassistant/components/whirlpool/climate.py +++ b/homeassistant/components/whirlpool/climate.py @@ -1,4 +1,6 @@ """Platform for climate integration.""" +from __future__ import annotations + import asyncio import logging @@ -13,15 +15,10 @@ from homeassistant.components.climate.const import ( FAN_LOW, FAN_MEDIUM, FAN_OFF, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE, SWING_HORIZONTAL, SWING_OFF, + ClimateEntityFeature, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -34,9 +31,9 @@ _LOGGER = logging.getLogger(__name__) AIRCON_MODE_MAP = { - AirconMode.Cool: HVAC_MODE_COOL, - AirconMode.Heat: HVAC_MODE_HEAT, - AirconMode.Fan: HVAC_MODE_FAN_ONLY, + AirconMode.Cool: HVACMode.COOL, + AirconMode.Heat: HVACMode.HEAT, + AirconMode.Fan: HVACMode.FAN_ONLY, } HVAC_MODE_TO_AIRCON_MODE = {v: k for k, v in AIRCON_MODE_MAP.items()} @@ -53,10 +50,10 @@ FAN_MODE_TO_AIRCON_FANSPEED = {v: k for k, v in AIRCON_FANSPEED_MAP.items()} SUPPORTED_FAN_MODES = [FAN_AUTO, FAN_HIGH, FAN_MEDIUM, FAN_LOW, FAN_OFF] SUPPORTED_HVAC_MODES = [ - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_OFF, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + HVACMode.OFF, ] SUPPORTED_MAX_TEMP = 30 SUPPORTED_MIN_TEMP = 16 @@ -89,7 +86,9 @@ class AirConEntity(ClimateEntity): _attr_max_temp = SUPPORTED_MAX_TEMP _attr_min_temp = SUPPORTED_MIN_TEMP _attr_supported_features = ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_SWING_MODE + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.SWING_MODE ) _attr_swing_modes = SUPPORTED_SWING_MODES _attr_target_temperature_step = SUPPORTED_TARGET_TEMPERATURE_STEP @@ -148,17 +147,17 @@ class AirConEntity(ClimateEntity): await self._aircon.set_humidity(humidity) @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode | None: """Return current operation ie. heat, cool, fan.""" if not self._aircon.get_power_on(): - return HVAC_MODE_OFF + return HVACMode.OFF mode: AirconMode = self._aircon.get_mode() return AIRCON_MODE_MAP.get(mode) - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set HVAC mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self._aircon.set_power_on(False) return diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index fbf5b0858a7..b96b4b89c61 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -12,12 +12,7 @@ from pywilight.const import ( WL_SPEED_MEDIUM, ) -from homeassistant.components.fan import ( - DIRECTION_FORWARD, - SUPPORT_DIRECTION, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import DIRECTION_FORWARD, FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -30,8 +25,6 @@ from . import DOMAIN, WiLightDevice ORDERED_NAMED_FAN_SPEEDS = [WL_SPEED_LOW, WL_SPEED_MEDIUM, WL_SPEED_HIGH] -SUPPORTED_FEATURES = SUPPORT_SET_SPEED | SUPPORT_DIRECTION - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -57,17 +50,14 @@ async def async_setup_entry( class WiLightFan(WiLightDevice, FanEntity): """Representation of a WiLights fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION + def __init__(self, api_device, index, item_name): """Initialize the device.""" super().__init__(api_device, index, item_name) # Initialize the WiLights fan. self._direction = WL_DIRECTION_FORWARD - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORTED_FEATURES - @property def icon(self): """Return the icon of device based on its type.""" diff --git a/homeassistant/components/wilight/light.py b/homeassistant/components/wilight/light.py index 3236b3b3851..10ff79fe60d 100644 --- a/homeassistant/components/wilight/light.py +++ b/homeassistant/components/wilight/light.py @@ -1,17 +1,10 @@ """Support for WiLight lights.""" -from pywilight.const import ( - ITEM_LIGHT, - LIGHT_COLOR, - LIGHT_DIMMER, - LIGHT_ON_OFF, - SUPPORT_NONE, -) +from pywilight.const import ITEM_LIGHT, LIGHT_COLOR, LIGHT_DIMMER, LIGHT_ON_OFF from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -56,10 +49,8 @@ async def async_setup_entry( class WiLightLightOnOff(WiLightDevice, LightEntity): """Representation of a WiLights light on-off.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_NONE + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} @property def is_on(self): @@ -78,10 +69,8 @@ class WiLightLightOnOff(WiLightDevice, LightEntity): class WiLightLightDimmer(WiLightDevice, LightEntity): """Representation of a WiLights light dimmer.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @property def brightness(self): @@ -131,10 +120,8 @@ def hass_to_wilight_saturation(value): class WiLightLightColor(WiLightDevice, LightEntity): """Representation of a WiLights light rgb.""" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} @property def brightness(self): diff --git a/homeassistant/components/wilight/translations/ca.json b/homeassistant/components/wilight/translations/ca.json index 0ace653e050..22044549b0c 100644 --- a/homeassistant/components/wilight/translations/ca.json +++ b/homeassistant/components/wilight/translations/ca.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Voleu configurar el WiLight {name}? \n\n Admet: {components}", + "description": "S'admeten els seg\u00fcents components: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/de.json b/homeassistant/components/wilight/translations/de.json index 546f8cec7b5..27d48a90e58 100644 --- a/homeassistant/components/wilight/translations/de.json +++ b/homeassistant/components/wilight/translations/de.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "M\u00f6chtest du WiLight {name} einrichten? \n\n Es unterst\u00fctzt: {components}", + "description": "Die folgenden Komponenten werden unterst\u00fctzt: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/en.json b/homeassistant/components/wilight/translations/en.json index 3d3a83a0270..1f0733d28f3 100644 --- a/homeassistant/components/wilight/translations/en.json +++ b/homeassistant/components/wilight/translations/en.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Do you want to set up WiLight {name}?\n\n It supports: {components}", + "description": "The following components are supported: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/et.json b/homeassistant/components/wilight/translations/et.json index 1be23313837..dae085d1084 100644 --- a/homeassistant/components/wilight/translations/et.json +++ b/homeassistant/components/wilight/translations/et.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Kas soovid seadistada WiLight'i {name} ?\n\n See toetab: {components}", + "description": "Toetatud on j\u00e4rgmised komponendid: {components}", "title": "" } } diff --git a/homeassistant/components/wilight/translations/fr.json b/homeassistant/components/wilight/translations/fr.json index 0a3851bb816..ddd85a5cc04 100644 --- a/homeassistant/components/wilight/translations/fr.json +++ b/homeassistant/components/wilight/translations/fr.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Voulez-vous configurer la WiLight {name}\u00a0?\n\n Elle prend en charge\u00a0: {components}", + "description": "Les composants suivants sont pris en charge\u00a0: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/hu.json b/homeassistant/components/wilight/translations/hu.json index 9ef669f1ed3..02db2f1b7df 100644 --- a/homeassistant/components/wilight/translations/hu.json +++ b/homeassistant/components/wilight/translations/hu.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani a WiLight {name}-t ? \n\nT\u00e1mogatja: {components}", + "description": "A k\u00f6vetkez\u0151 komponensek t\u00e1mogatottak: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/id.json b/homeassistant/components/wilight/translations/id.json index 06616b29e35..0489a0e96bb 100644 --- a/homeassistant/components/wilight/translations/id.json +++ b/homeassistant/components/wilight/translations/id.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Apakah Anda ingin menyiapkan WiLight {name}?\n\nIni mendukung: {components}", + "description": "Komponen berikut didukung: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/it.json b/homeassistant/components/wilight/translations/it.json index 84b323a0000..7b560b7bfdb 100644 --- a/homeassistant/components/wilight/translations/it.json +++ b/homeassistant/components/wilight/translations/it.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Vuoi configurare WiLight {name}? \n\nSupporta: {components}", + "description": "Sono supportati i seguenti componenti: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/nl.json b/homeassistant/components/wilight/translations/nl.json index c2820f0ed6e..51cefe8ce28 100644 --- a/homeassistant/components/wilight/translations/nl.json +++ b/homeassistant/components/wilight/translations/nl.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Wil je WiLight {name} ? \n\n Het ondersteunt: {components}", + "description": "De volgende componenten worden ondersteund: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/no.json b/homeassistant/components/wilight/translations/no.json index 170739145da..582b2289a32 100644 --- a/homeassistant/components/wilight/translations/no.json +++ b/homeassistant/components/wilight/translations/no.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Vil du konfigurere WiLight {name} ? \n\n Den st\u00f8tter: {components}", + "description": "F\u00f8lgende komponenter st\u00f8ttes: {components}", "title": "" } } diff --git a/homeassistant/components/wilight/translations/pl.json b/homeassistant/components/wilight/translations/pl.json index 93957d016f5..c1f636b9d80 100644 --- a/homeassistant/components/wilight/translations/pl.json +++ b/homeassistant/components/wilight/translations/pl.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 WiLight {name}?\n\nObs\u0142uguje: {components}", + "description": "Obs\u0142ugiwane s\u0105 nast\u0119puj\u0105ce komponenty: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/pt-BR.json b/homeassistant/components/wilight/translations/pt-BR.json index 5da689b9b74..e70a286e812 100644 --- a/homeassistant/components/wilight/translations/pt-BR.json +++ b/homeassistant/components/wilight/translations/pt-BR.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "Voc\u00ea deseja configurar WiLight {name}?\n\nEle suporta: {components}", + "description": "Os seguintes componentes s\u00e3o compat\u00edveis: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/ru.json b/homeassistant/components/wilight/translations/ru.json index 7d04a13518d..7873b8ca326 100644 --- a/homeassistant/components/wilight/translations/ru.json +++ b/homeassistant/components/wilight/translations/ru.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c WiLight {name}? \n\n\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442: {components}", + "description": "\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b: {components}.", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/tr.json b/homeassistant/components/wilight/translations/tr.json index 446504f49d7..4762a678254 100644 --- a/homeassistant/components/wilight/translations/tr.json +++ b/homeassistant/components/wilight/translations/tr.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "{name} kurmak istiyor musunuz? \n\n \u015eunlar\u0131 destekler: {components}", + "description": "A\u015fa\u011f\u0131daki bile\u015fenler desteklenir: {components}", "title": "WiLight" } } diff --git a/homeassistant/components/wilight/translations/zh-Hant.json b/homeassistant/components/wilight/translations/zh-Hant.json index fe6c36f21d2..70a9bff0550 100644 --- a/homeassistant/components/wilight/translations/zh-Hant.json +++ b/homeassistant/components/wilight/translations/zh-Hant.json @@ -8,7 +8,7 @@ "flow_title": "{name}", "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a WiLight {name}\uff1f\n\n\u652f\u63f4\uff1a{components}", + "description": "\u652f\u63f4\u4ee5\u4e0b\u5143\u4ef6\uff1a{components}", "title": "WiLight" } } diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index c94c518709d..86dd5f2802b 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -186,7 +186,6 @@ class WirelessTagBaseSensor(Entity): """ return self.decorate_value(self.principal_value) - # pylint: disable=no-self-use def decorate_value(self, value): """Decorate input value to be well presented for end user.""" return f"{value:.1f}" diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 8682df59e0d..f2fe7bfb1ec 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -641,7 +641,6 @@ class DataManager: Withings' API occasionally and incorrectly throws errors. Retrying the call tends to work. """ - # pylint: disable=no-self-use exception = None for attempt in range(1, attempts + 1): _LOGGER.debug("Attempt %s of %s", attempt, attempts) diff --git a/homeassistant/components/withings/manifest.json b/homeassistant/components/withings/manifest.json index 8337068ba68..2e089bdbcc9 100644 --- a/homeassistant/components/withings/manifest.json +++ b/homeassistant/components/withings/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/withings", "requirements": ["withings-api==2.4.0"], - "dependencies": ["http", "webhook"], + "dependencies": ["auth", "http", "webhook"], "codeowners": ["@vangorra"], "iot_class": "cloud_polling", "loggers": ["withings_api"] diff --git a/homeassistant/components/withings/translations/hu.json b/homeassistant/components/withings/translations/hu.json index 1a64a95de5e..7504c36c58e 100644 --- a/homeassistant/components/withings/translations/hu.json +++ b/homeassistant/components/withings/translations/hu.json @@ -4,7 +4,7 @@ "already_configured": "A profil konfigur\u00e1ci\u00f3ja friss\u00edtve.", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rem, k\u00f6vesse a dokument\u00e1ci\u00f3t.", - "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3 [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lhat\u00f3." }, "create_entry": { "default": "A Withings sikeresen hiteles\u00edtett." @@ -15,7 +15,7 @@ "flow_title": "{profile}", "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" }, "profile": { "data": { diff --git a/homeassistant/components/withings/translations/it.json b/homeassistant/components/withings/translations/it.json index f97933593a0..079acb0b503 100644 --- a/homeassistant/components/withings/translations/it.json +++ b/homeassistant/components/withings/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Configurazione aggiornata per il profilo.", "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})" }, "create_entry": { diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 9b2d7e6fab4..084acbe73e7 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -13,12 +13,9 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_RGBW, - COLOR_MODE_RGBWW, - SUPPORT_EFFECT, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -32,7 +29,7 @@ from .const import DOMAIN from .entity import WizToggleEntity from .models import WizData -RGB_WHITE_CHANNELS_COLOR_MODE = {1: COLOR_MODE_RGBW, 2: COLOR_MODE_RGBWW} +RGB_WHITE_CHANNELS_COLOR_MODE = {1: ColorMode.RGBW, 2: ColorMode.RGBWW} def _async_pilot_builder(**kwargs: Any) -> PilotBuilder: @@ -79,21 +76,22 @@ class WizBulbEntity(WizToggleEntity, LightEntity): super().__init__(wiz_data, name) bulb_type: BulbType = self._device.bulbtype features: Features = bulb_type.features - color_modes = set() + self._attr_supported_color_modes: set[ColorMode | str] = set() if features.color: - color_modes.add(RGB_WHITE_CHANNELS_COLOR_MODE[bulb_type.white_channels]) + self._attr_supported_color_modes.add( + RGB_WHITE_CHANNELS_COLOR_MODE[bulb_type.white_channels] + ) if features.color_tmp: - color_modes.add(COLOR_MODE_COLOR_TEMP) - if not color_modes and features.brightness: - color_modes.add(COLOR_MODE_BRIGHTNESS) - self._attr_supported_color_modes = color_modes + self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) + if not self._attr_supported_color_modes and features.brightness: + self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) self._attr_effect_list = wiz_data.scenes if bulb_type.bulb_type != BulbClass.DW: kelvin = bulb_type.kelvin_range self._attr_min_mireds = color_temperature_kelvin_to_mired(kelvin.max) self._attr_max_mireds = color_temperature_kelvin_to_mired(kelvin.min) if bulb_type.features.effect: - self._attr_supported_features = SUPPORT_EFFECT + self._attr_supported_features = LightEntityFeature.EFFECT self._async_update_attrs() @callback @@ -104,21 +102,21 @@ class WizBulbEntity(WizToggleEntity, LightEntity): assert color_modes is not None if (brightness := state.get_brightness()) is not None: self._attr_brightness = max(0, min(255, brightness)) - if COLOR_MODE_COLOR_TEMP in color_modes and ( + if ColorMode.COLOR_TEMP in color_modes and ( color_temp := state.get_colortemp() ): - self._attr_color_mode = COLOR_MODE_COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) elif ( - COLOR_MODE_RGBWW in color_modes and (rgbww := state.get_rgbww()) is not None + ColorMode.RGBWW in color_modes and (rgbww := state.get_rgbww()) is not None ): self._attr_rgbww_color = rgbww - self._attr_color_mode = COLOR_MODE_RGBWW - elif COLOR_MODE_RGBW in color_modes and (rgbw := state.get_rgbw()) is not None: + self._attr_color_mode = ColorMode.RGBWW + elif ColorMode.RGBW in color_modes and (rgbw := state.get_rgbw()) is not None: self._attr_rgbw_color = rgbw - self._attr_color_mode = COLOR_MODE_RGBW + self._attr_color_mode = ColorMode.RGBW else: - self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_color_mode = ColorMode.BRIGHTNESS self._attr_effect = state.get_scene() super()._async_update_attrs() diff --git a/homeassistant/components/wiz/translations/hu.json b/homeassistant/components/wiz/translations/hu.json index 7c99571a9ae..63ae8479bb9 100644 --- a/homeassistant/components/wiz/translations/hu.json +++ b/homeassistant/components/wiz/translations/hu.json @@ -28,7 +28,7 @@ "user": { "data": { "host": "IP c\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" }, "description": "Ha az IP-c\u00edmet \u00fcresen hagyja, akkor az eszk\u00f6z\u00f6k keres\u00e9se a felder\u00edt\u00e9ssel t\u00f6rt\u00e9nik." } diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index b30e20810d9..4f5c758dfff 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -10,12 +10,9 @@ from homeassistant.components.light import ( ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_TRANSITION, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_RGB, - COLOR_MODE_RGBW, - SUPPORT_EFFECT, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -53,16 +50,16 @@ async def async_setup_entry( class WLEDMasterLight(WLEDEntity, LightEntity): """Defines a WLED master light.""" - _attr_color_mode = COLOR_MODE_BRIGHTNESS + _attr_color_mode = ColorMode.BRIGHTNESS _attr_icon = "mdi:led-strip-variant" - _attr_supported_features = SUPPORT_TRANSITION + _attr_supported_features = LightEntityFeature.TRANSITION + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None: """Initialize WLED master light.""" super().__init__(coordinator=coordinator) self._attr_name = f"{coordinator.data.info.name} Master" self._attr_unique_id = coordinator.data.info.mac_address - self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} @property def brightness(self) -> int | None: @@ -105,7 +102,7 @@ class WLEDMasterLight(WLEDEntity, LightEntity): class WLEDSegmentLight(WLEDEntity, LightEntity): """Defines a WLED light based on a segment.""" - _attr_supported_features = SUPPORT_EFFECT | SUPPORT_TRANSITION + _attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION _attr_icon = "mdi:led-strip-variant" def __init__( @@ -129,11 +126,11 @@ class WLEDSegmentLight(WLEDEntity, LightEntity): f"{self.coordinator.data.info.mac_address}_{self._segment}" ) - self._attr_color_mode = COLOR_MODE_RGB - self._attr_supported_color_modes = {COLOR_MODE_RGB} + self._attr_color_mode = ColorMode.RGB + self._attr_supported_color_modes = {ColorMode.RGB} if self._rgbw and self._wv: - self._attr_color_mode = COLOR_MODE_RGBW - self._attr_supported_color_modes = {COLOR_MODE_RGBW} + self._attr_color_mode = ColorMode.RGBW + self._attr_supported_color_modes = {ColorMode.RGBW} @property def available(self) -> bool: diff --git a/homeassistant/components/wolflink/translations/sensor.hu.json b/homeassistant/components/wolflink/translations/sensor.hu.json index 0a257e570cf..71e8f1e42ca 100644 --- a/homeassistant/components/wolflink/translations/sensor.hu.json +++ b/homeassistant/components/wolflink/translations/sensor.hu.json @@ -65,7 +65,7 @@ "sparen": "Gazdas\u00e1gos", "spreizung_hoch": "dT t\u00fal sz\u00e9les", "spreizung_kf": "Spread KF", - "stabilisierung": "Stabiliz\u00e1ci\u00f3", + "stabilisierung": "Stabiliz\u00e1l\u00e1s", "standby": "K\u00e9szenl\u00e9t", "start": "Indul\u00e1s", "storung": "Hiba", diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py index 0676b249e1b..42a15a643bd 100644 --- a/homeassistant/components/x10/light.py +++ b/homeassistant/components/x10/light.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, + ColorMode, LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_ID, CONF_NAME @@ -20,8 +20,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) -SUPPORT_X10 = SUPPORT_BRIGHTNESS - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_DEVICES): vol.All( @@ -63,6 +61,9 @@ def setup_platform( class X10Light(LightEntity): """Representation of an X10 Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + def __init__(self, light, is_cm11a): """Initialize an X10 Light.""" self._name = light["name"] @@ -86,11 +87,6 @@ class X10Light(LightEntity): """Return true if light is on.""" return self._state - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_X10 - def turn_on(self, **kwargs): """Instruct the light to turn on.""" if self._is_cm11a: diff --git a/homeassistant/components/xbee/light.py b/homeassistant/components/xbee/light.py index 93f5f866f2f..126eaf91f9d 100644 --- a/homeassistant/components/xbee/light.py +++ b/homeassistant/components/xbee/light.py @@ -3,7 +3,7 @@ from __future__ import annotations import voluptuous as vol -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -29,3 +29,6 @@ def setup_platform( class XBeeLight(XBeeDigitalOut, LightEntity): """Use XBeeDigitalOut as light.""" + + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} diff --git a/homeassistant/components/xbox/manifest.json b/homeassistant/components/xbox/manifest.json index d02f9cc2279..432b3e84100 100644 --- a/homeassistant/components/xbox/manifest.json +++ b/homeassistant/components/xbox/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xbox", "requirements": ["xbox-webapi==2.0.11"], - "dependencies": ["http"], + "dependencies": ["auth"], "codeowners": ["@hunterjm"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/xbox/media_player.py b/homeassistant/components/xbox/media_player.py index edf29c164ee..ae361d6806e 100644 --- a/homeassistant/components/xbox/media_player.py +++ b/homeassistant/components/xbox/media_player.py @@ -13,21 +13,11 @@ from xbox.webapi.api.provider.smartglass.models import ( VolumeDirection, ) -from homeassistant.components.media_player import MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_APP, - MEDIA_TYPE_GAME, - SUPPORT_BROWSE_MEDIA, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_APP, MEDIA_TYPE_GAME from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant @@ -40,16 +30,16 @@ from .browse_media import build_item_response from .const import DOMAIN SUPPORT_XBOX = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY - | SUPPORT_PAUSE - | SUPPORT_VOLUME_STEP - | SUPPORT_VOLUME_MUTE - | SUPPORT_BROWSE_MEDIA - | SUPPORT_PLAY_MEDIA + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.BROWSE_MEDIA + | MediaPlayerEntityFeature.PLAY_MEDIA ) XBOX_STATE_MAP = { @@ -119,7 +109,11 @@ class XboxMediaPlayer(CoordinatorEntity[XboxUpdateCoordinator], MediaPlayerEntit def supported_features(self): """Flag media player features that are supported.""" if self.state not in [STATE_PLAYING, STATE_PAUSED]: - return SUPPORT_XBOX & ~SUPPORT_NEXT_TRACK & ~SUPPORT_PREVIOUS_TRACK + return ( + SUPPORT_XBOX + & ~MediaPlayerEntityFeature.NEXT_TRACK + & ~MediaPlayerEntityFeature.PREVIOUS_TRACK + ) return SUPPORT_XBOX @property diff --git a/homeassistant/components/xbox/translations/hu.json b/homeassistant/components/xbox/translations/hu.json index 24c46bb8ab0..fc8428b3101 100644 --- a/homeassistant/components/xbox/translations/hu.json +++ b/homeassistant/components/xbox/translations/hu.json @@ -10,7 +10,7 @@ }, "step": { "pick_implementation": { - "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lasszon egy hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/xbox/translations/it.json b/homeassistant/components/xbox/translations/it.json index 010baad571e..e60c37c9e5f 100644 --- a/homeassistant/components/xbox/translations/it.json +++ b/homeassistant/components/xbox/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", - "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Segui la documentazione.", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { diff --git a/homeassistant/components/xiaomi_aqara/light.py b/homeassistant/components/xiaomi_aqara/light.py index d6de34cf04c..812cd1c96b8 100644 --- a/homeassistant/components/xiaomi_aqara/light.py +++ b/homeassistant/components/xiaomi_aqara/light.py @@ -6,8 +6,7 @@ import struct from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -41,6 +40,9 @@ async def async_setup_entry( class XiaomiGatewayLight(XiaomiDevice, LightEntity): """Representation of a XiaomiGatewayLight.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, device, name, xiaomi_hub, config_entry): """Initialize the XiaomiGatewayLight.""" self._data_key = "rgb" @@ -94,11 +96,6 @@ class XiaomiGatewayLight(XiaomiDevice, LightEntity): """Return the hs color value.""" return self._hs - @property - def supported_features(self): - """Return the supported features.""" - return SUPPORT_BRIGHTNESS | SUPPORT_COLOR - def turn_on(self, **kwargs): """Turn the light on.""" if ATTR_HS_COLOR in kwargs: diff --git a/homeassistant/components/xiaomi_aqara/translations/ca.json b/homeassistant/components/xiaomi_aqara/translations/ca.json index 46037fa5eea..78f5affd556 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ca.json +++ b/homeassistant/components/xiaomi_aqara/translations/ca.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Adre\u00e7a IP" }, - "description": "Torna a executar la configuraci\u00f3 si vols connectar passarel\u00b7les addicionals", + "description": "Selecciona la passarel\u00b7la Xiaomi Aqara a la qual connectar-te", "title": "Selecciona la passarel\u00b7la Xiaomi Aqara a la qual connectar-te" }, "settings": { @@ -27,7 +27,7 @@ "name": "Nom de la passarel\u00b7la" }, "description": "La clau (contrasenya) es pot obtenir mitjan\u00e7ant aquest tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Si no es proporciona la clau, nom\u00e9s seran accessibles els sensors", - "title": "Passarel\u00b7la Xiaomi Aqara, configuraci\u00f3 opcional" + "title": "Configuracions opcionals" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Interf\u00edcie de xarxa a utilitzar", "mac": "Adre\u00e7a MAC (opcional)" }, - "description": "Connecta't a la teva passarel\u00b7la Xiaomi Aqara, si les adreces IP i MAC es deixen buides s'utilitzar\u00e0 els descobriment autom\u00e0tic", + "description": "Si les adreces IP i MAC es deixen buides s'utilitzar\u00e0 el descobriment autom\u00e0tic", "title": "Passarel\u00b7la Xiaomi Aqara" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index 469fa14bcc1..170442fc856 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP-Adresse" }, - "description": "F\u00fchre das Setup erneut aus, wenn du zus\u00e4tzliche Gateways verbinden m\u00f6chtest", + "description": "W\u00e4hle das Xiaomi Aqara Gateway, das du verbinden m\u00f6chtest", "title": "W\u00e4hle das Xiaomi Aqara Gateway, das du verbinden m\u00f6chtest" }, "settings": { @@ -27,7 +27,7 @@ "name": "Name des Gateways" }, "description": "Der Schl\u00fcssel (das Passwort) kann mithilfe dieser Anleitung abgerufen werden: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Wenn der Schl\u00fcssel nicht angegeben wird, sind nur die Sensoren zug\u00e4nglich", - "title": "Xiaomi Aqara Gateway, optionale Einstellungen" + "title": "Optionale Einstellungen" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Die zu verwendende Netzwerkschnittstelle", "mac": "MAC-Adresse (optional)" }, - "description": "Stelle eine Verbindung zu deinem Xiaomi Aqara Gateway her. Wenn die IP- und MAC-Adressen leer bleiben, wird die automatische Erkennung verwendet", + "description": "Wenn die IP- und MAC-Adressen leer gelassen werden, wird die automatische Erkennung verwendet", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/en.json b/homeassistant/components/xiaomi_aqara/translations/en.json index 53776111d37..72949820bc0 100644 --- a/homeassistant/components/xiaomi_aqara/translations/en.json +++ b/homeassistant/components/xiaomi_aqara/translations/en.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP Address" }, - "description": "Run the setup again if you want to connect additional gateways", + "description": "Select the Xiaomi Aqara Gateway that you wish to connect", "title": "Select the Xiaomi Aqara Gateway that you wish to connect" }, "settings": { @@ -27,7 +27,7 @@ "name": "Name of the Gateway" }, "description": "The key (password) can be retrieved using this tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. If the key is not provided only sensors will be accessible", - "title": "Xiaomi Aqara Gateway, optional settings" + "title": "Optional settings" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "The network interface to use", "mac": "Mac Address (optional)" }, - "description": "Connect to your Xiaomi Aqara Gateway, if the IP and MAC addresses are left empty, auto-discovery is used", + "description": "If the IP and MAC addresses are left empty, auto-discovery is used", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/et.json b/homeassistant/components/xiaomi_aqara/translations/et.json index cc94b0e9b95..087c59dd2e2 100644 --- a/homeassistant/components/xiaomi_aqara/translations/et.json +++ b/homeassistant/components/xiaomi_aqara/translations/et.json @@ -18,7 +18,7 @@ "data": { "select_ip": "L\u00fc\u00fcsi IP aadress" }, - "description": "K\u00e4ivita seadistamine uuesti kui soovid \u00fchendada t\u00e4iendavaid l\u00fc\u00fcse", + "description": "Vali Xiaomi Aqara Gateway mida soovid \u00fchendada.", "title": "Vali Xiaomi Aqara l\u00fc\u00fcs mida soovid \u00fchendada" }, "settings": { @@ -27,7 +27,7 @@ "name": "L\u00fc\u00fcsi nimi" }, "description": "V\u00f5tme (parooli) saab hankida selle \u00f5petuse abil: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Kui v\u00f5ti puudub on ligip\u00e4\u00e4s ainult anduritele", - "title": "Xiaomi Aqara Gateway, valikulised s\u00e4tted" + "title": "Valikulised s\u00e4tted" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Kasutatav v\u00f5rguliides", "mac": "MAC aadress (valikuline)" }, - "description": "\u00dchendu oma Xiaomi Aqara Gatewayga. Kui IP- ja MAC-aadressid j\u00e4etakse t\u00fchjaks, kasutatakse automaatset avastamist", + "description": "Kui IP- ja MAC-aadressid j\u00e4etakse t\u00fchjaks, kasutatakse automaatset avastamist", "title": "" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/fr.json b/homeassistant/components/xiaomi_aqara/translations/fr.json index 04be4e35e2f..ab042cb5f0e 100644 --- a/homeassistant/components/xiaomi_aqara/translations/fr.json +++ b/homeassistant/components/xiaomi_aqara/translations/fr.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Adresse IP" }, - "description": "Ex\u00e9cutez \u00e0 nouveau la configuration si vous souhaitez connecter des passerelles suppl\u00e9mentaires", + "description": "S\u00e9lectionnez la passerelle Xiaomi Aqara que vous souhaitez connecter", "title": "S\u00e9lectionnez la passerelle Xiaomi Aqara que vous souhaitez connecter" }, "settings": { @@ -27,7 +27,7 @@ "name": "Nom de la passerelle" }, "description": "La cl\u00e9 (mot de passe) peut \u00eatre r\u00e9cup\u00e9r\u00e9e \u00e0 l'aide de ce tutoriel: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Si la cl\u00e9 n'est pas fournie, seuls les capteurs seront accessibles", - "title": "Passerelle Xiaomi Aqara, param\u00e8tres optionnels" + "title": "Param\u00e8tres optionnels" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Interface r\u00e9seau \u00e0 utiliser", "mac": "Adresse MAC (facultatif)" }, - "description": "Connectez-vous \u00e0 votre passerelle Xiaomi Aqara, si les adresses IP et mac sont laiss\u00e9es vides, la d\u00e9tection automatique est utilis\u00e9e", + "description": "Si les adresses IP et MAC sont laiss\u00e9es vides, la d\u00e9couverte automatique sera utilis\u00e9e", "title": "Passerelle Xiaomi Aqara" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/he.json b/homeassistant/components/xiaomi_aqara/translations/he.json index 7450bbd463c..ea6cd8e278b 100644 --- a/homeassistant/components/xiaomi_aqara/translations/he.json +++ b/homeassistant/components/xiaomi_aqara/translations/he.json @@ -27,7 +27,7 @@ "name": "\u05e9\u05dd \u05d4\u05e9\u05e2\u05e8" }, "description": "\u05e0\u05d9\u05ea\u05df \u05dc\u05d0\u05d7\u05d6\u05e8 \u05d0\u05ea \u05d4\u05de\u05e4\u05ea\u05d7 (\u05e1\u05d9\u05e1\u05de\u05d4) \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea \u05d4\u05d3\u05e8\u05db\u05d4 \u05d6\u05d5: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. \u05d0\u05dd \u05d4\u05de\u05e4\u05ea\u05d7 \u05d0\u05d9\u05e0\u05d5 \u05de\u05e1\u05d5\u05e4\u05e7, \u05e8\u05e7 \u05d7\u05d9\u05d9\u05e9\u05e0\u05d9\u05dd \u05d9\u05d4\u05d9\u05d5 \u05e0\u05d2\u05d9\u05e9\u05d9\u05dd", - "title": "\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05d0\u05e7\u05d0\u05e8\u05d4, \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9\u05d5\u05ea" + "title": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9\u05d5\u05ea" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "\u05de\u05de\u05e9\u05e7 \u05d4\u05e8\u05e9\u05ea \u05d1\u05d5 \u05d9\u05e9 \u05dc\u05d4\u05e9\u05ea\u05de\u05e9", "mac": "\u05db\u05ea\u05d5\u05d1\u05ea Mac (\u05d0\u05d5\u05e4\u05e6\u05d9\u05d5\u05e0\u05dc\u05d9)" }, - "description": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d5\u05d0\u05de\u05d9 \u05d0\u05e7\u05d0\u05e8\u05d4 \u05e9\u05dc\u05da, \u05d0\u05dd \u05db\u05ea\u05d5\u05d1\u05d5\u05ea \u05d4-IP \u05d5\u05d4-MAC \u05d9\u05d5\u05d5\u05ea\u05e8\u05d5 \u05e8\u05d9\u05e7\u05d5\u05ea, \u05e0\u05e2\u05e9\u05d4 \u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05d2\u05d9\u05dc\u05d5\u05d9 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9", + "description": "\u05d0\u05dd \u05db\u05ea\u05d5\u05d1\u05d5\u05ea \u05d4-IP \u05d5\u05d4-MAC \u05e0\u05d5\u05ea\u05e8\u05d5\u05ea \u05e8\u05d9\u05e7\u05d5\u05ea, \u05e0\u05e2\u05e9\u05d4 \u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05d2\u05d9\u05dc\u05d5\u05d9 \u05d0\u05d5\u05d8\u05d5\u05de\u05d8\u05d9", "title": "\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9 \u05d0\u05e7\u05d0\u05e8\u05d4" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/hu.json b/homeassistant/components/xiaomi_aqara/translations/hu.json index e38bebefe19..f0a1b076750 100644 --- a/homeassistant/components/xiaomi_aqara/translations/hu.json +++ b/homeassistant/components/xiaomi_aqara/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "not_xiaomi_aqara": "Nem egy Xiaomi Aqara Gateway, a felfedezett eszk\u00f6z nem egyezett az ismert \u00e1tj\u00e1r\u00f3kkal" }, "error": { @@ -18,7 +18,7 @@ "data": { "select_ip": "IP c\u00edm" }, - "description": "Futtassa \u00fajra a be\u00e1ll\u00edt\u00e1st, ha egy m\u00e1sik k\u00f6zponti egys\u00e9get szeretne csatlakoztatni", + "description": "V\u00e1lassza ki a csatlakoztatni k\u00edv\u00e1nt Xiaomi Aqara K\u00f6zponti egys\u00e9get", "title": "V\u00e1lassza ki a csatlakoztatni k\u00edv\u00e1nt Xiaomi Aqara K\u00f6zponti egys\u00e9get" }, "settings": { @@ -27,7 +27,7 @@ "name": "K\u00f6zponti egys\u00e9g neve" }, "description": "A kulcs (jelsz\u00f3) az al\u00e1bbi oktat\u00f3anyag seg\u00edts\u00e9g\u00e9vel t\u00f6lthet\u0151 le: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Ha a kulcs nincs megadva, csak az \u00e9rz\u00e9kel\u0151k lesznek hozz\u00e1f\u00e9rhet\u0151k", - "title": "Xiaomi Aqara k\u00f6zponti egys\u00e9g, opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok" + "title": "Opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "A haszn\u00e1lni k\u00edv\u00e1nt h\u00e1l\u00f3zati interf\u00e9sz", "mac": "Mac-c\u00edm (opcion\u00e1lis)" }, - "description": "Csatlakozzon a Xiaomi Aqara k\u00f6zponti egys\u00e9ghez, ha az IP- \u00e9s a MAC-c\u00edm \u00fcresen marad, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja", + "description": "Ha az IP- \u00e9s MAC-c\u00edm mez\u0151ket \u00fcresen hagyja, akkor az automatikus felder\u00edt\u00e9s ker\u00fcl alkalmaz\u00e1sra.", "title": "Xiaomi Aqara k\u00f6zponti egys\u00e9g" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/id.json b/homeassistant/components/xiaomi_aqara/translations/id.json index eeab548f681..2b33af8237f 100644 --- a/homeassistant/components/xiaomi_aqara/translations/id.json +++ b/homeassistant/components/xiaomi_aqara/translations/id.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Alamat IP" }, - "description": "Jalankan penyiapan lagi jika Anda ingin menghubungkan gateway lainnya", + "description": "Pilih Gateway Xiaomi Aqara yang ingin disambungkan", "title": "Pilih Gateway Xiaomi Aqara yang ingin dihubungkan" }, "settings": { @@ -27,7 +27,7 @@ "name": "Nama Gateway" }, "description": "Kunci (kata sandi) dapat diambil menggunakan tutorial ini: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Jika kunci tidak disediakan, hanya sensor yang akan dapat diakses", - "title": "Xiaomi Aqara Gateway, pengaturan opsional" + "title": "Pengaturan opsional" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Antarmuka jaringan yang akan digunakan", "mac": "Alamat MAC (opsional)" }, - "description": "Hubungkan ke Xiaomi Aqara Gateway Anda, jika alamat IP dan MAC dibiarkan kosong, penemuan otomatis digunakan", + "description": "Jika alamat IP dan MAC dikosongkan, penemuan otomatis digunakan", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/it.json b/homeassistant/components/xiaomi_aqara/translations/it.json index 319a33f3964..ac5eff78190 100644 --- a/homeassistant/components/xiaomi_aqara/translations/it.json +++ b/homeassistant/components/xiaomi_aqara/translations/it.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Indirizzo IP" }, - "description": "Esegui di nuovo la configurazione se desideri connettere gateway aggiuntivi", + "description": "Seleziona lo Xiaomi Aqara Gateway che desideri connettere", "title": "Seleziona il Gateway Xiaomi Aqara che si desidera collegare" }, "settings": { @@ -27,7 +27,7 @@ "name": "Nome del Gateway" }, "description": "La chiave (password) pu\u00f2 essere recuperata utilizzando questo tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Se la chiave non viene fornita, saranno accessibili solo i sensori", - "title": "Xiaomi Aqara Gateway, impostazioni opzionali" + "title": "Impostazioni facoltative" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "L'interfaccia di rete da utilizzare", "mac": "Indirizzo Mac (opzionale)" }, - "description": "Connettiti al tuo Xiaomi Aqara Gateway, se gli indirizzi IP e MAC sono lasciati vuoti, sar\u00e0 utilizzato il rilevamento automatico", + "description": "Se gli indirizzi IP e MAC vengono lasciati vuoti, viene utilizzato il rilevamento automatico", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index 9ef660a8e7f..c2042850fdb 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP-adres" }, - "description": "Voer de installatie opnieuw uit als u extra gateways wilt aansluiten", + "description": "Selecteer de Xiaomi Aqara Gateway die u wilt verbinden", "title": "Selecteer de Xiaomi Aqara Gateway waarmee u verbinding wilt maken" }, "settings": { @@ -27,7 +27,7 @@ "name": "Naam van de Gateway" }, "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk", - "title": "Xiaomi Aqara Gateway, optionele instellingen" + "title": "Optionele instellingen" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "De netwerkinterface die moet worden gebruikt", "mac": "MAC-adres (optioneel)" }, - "description": "Maak verbinding met uw Xiaomi Aqara Gateway, als de IP- en mac-adressen leeg worden gelaten, wordt automatische detectie gebruikt", + "description": "Als de IP- en MAC-adressen leeg worden gelaten, wordt auto-discovery gebruikt", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/no.json b/homeassistant/components/xiaomi_aqara/translations/no.json index 081b0e5e990..c325886dd22 100644 --- a/homeassistant/components/xiaomi_aqara/translations/no.json +++ b/homeassistant/components/xiaomi_aqara/translations/no.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP adresse" }, - "description": "Kj\u00f8r oppsettet p\u00e5 nytt hvis du vil koble til flere gatewayer", + "description": "Velg Xiaomi Aqara Gateway som du \u00f8nsker \u00e5 koble til", "title": "Velg Xiaomi Aqara Gateway som du \u00f8nsker \u00e5 koble til" }, "settings": { @@ -27,7 +27,7 @@ "name": "Navnet p\u00e5 gatewayen" }, "description": "N\u00f8kkelen (passordet) kan hentes ved hjelp av denne veiviseren: [https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz](https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz). Hvis n\u00f8kkelen ikke oppgis, vil bare sensorer bli tilgjengelige", - "title": "Xiaomi Aqara Gateway, valgfrie innstillinger" + "title": "Valgfrie innstillinger" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Nettverksgrensesnittet som skal brukes", "mac": "MAC-adresse (valgfritt)" }, - "description": "Koble til Xiaomi Aqara Gateway, hvis IP- og MAC-adressene blir tomme, brukes automatisk oppdagelse", + "description": "Hvis IP- og MAC-adressene er tomme, brukes automatisk oppdagelse", "title": "" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/pl.json b/homeassistant/components/xiaomi_aqara/translations/pl.json index da8a803d81f..e680415320c 100644 --- a/homeassistant/components/xiaomi_aqara/translations/pl.json +++ b/homeassistant/components/xiaomi_aqara/translations/pl.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Adres IP" }, - "description": "Uruchom konfiguracj\u0119 ponownie, je\u015bli chcesz pod\u0142\u0105czy\u0107 dodatkowe bramki", + "description": "Wybierz bramk\u0119 Xiaomi Aqara, z kt\u00f3r\u0105 chcesz si\u0119 po\u0142\u0105czy\u0107", "title": "Wybierz bramk\u0119 Xiaomi Aqara, z kt\u00f3r\u0105 chcesz si\u0119 po\u0142\u0105czy\u0107" }, "settings": { @@ -27,7 +27,7 @@ "name": "Nazwa bramki" }, "description": "Klucz (has\u0142o) mo\u017cna uzyska\u0107 za pomoc\u0105 tej instrukcji: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Je\u015bli klucz nie zostanie podany, dost\u0119pne b\u0119d\u0105 tylko sensory.", - "title": "Brama Xiaomi Aqara, ustawienia opcjonalne" + "title": "Ustawienia opcjonalne" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Interfejs sieciowy", "mac": "Adres MAC (opcjonalnie)" }, - "description": "Po\u0142\u0105cz si\u0119 z bramk\u0105 Xiaomi Aqara, je\u015bli zostawisz Adres IP oraz MAC puste to bramka zostanie automatycznie wykryta.", + "description": "Je\u015bli zostawisz Adres IP oraz MAC puste to bramka zostanie automatycznie wykryta.", "title": "Bramka Xiaomi Aqara" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json index 5e188e54565..62deb3ba97c 100644 --- a/homeassistant/components/xiaomi_aqara/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_aqara/translations/pt-BR.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Endere\u00e7o IP" }, - "description": "Execute a configura\u00e7\u00e3o novamente se quiser conectar gateways adicionais", + "description": "Selecione o Xiaomi Aqara Gateway que voc\u00ea deseja conectar", "title": "Selecione o Xiaomi Aqara Gateway que voc\u00ea deseja conectar" }, "settings": { @@ -27,15 +27,15 @@ "name": "Nome do Gateway" }, "description": "A chave (senha) pode ser recuperada usando este tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Se a chave n\u00e3o for fornecida, apenas os sensores estar\u00e3o acess\u00edveis", - "title": "Xiaomi Aqara Gateway, configura\u00e7\u00f5es opcionais" + "title": "Configura\u00e7\u00f5es opcionais" }, "user": { "data": { - "host": "Endere\u00e7o IP", + "host": "Endere\u00e7o IP (opcional)", "interface": "A interface de rede a ser usada", "mac": "Endere\u00e7o Mac (opcional)" }, - "description": "Conecte-se ao seu Xiaomi Aqara Gateway, se os endere\u00e7os IP e MAC ficarem vazios, a descoberta autom\u00e1tica \u00e9 usada", + "description": "Se os endere\u00e7os IP e MAC forem deixados vazios, a descoberta autom\u00e1tica \u00e9 usada", "title": "Gateway Xiaomi Aqara" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/ru.json b/homeassistant/components/xiaomi_aqara/translations/ru.json index aa18808d222..499837889df 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ru.json +++ b/homeassistant/components/xiaomi_aqara/translations/ru.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0435\u0441\u043b\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0448\u043b\u044e\u0437.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara.", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara" }, "settings": { @@ -27,7 +27,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, "description": "\u041a\u043b\u044e\u0447 (\u043f\u0430\u0440\u043e\u043b\u044c) \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0433\u043e \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0430: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. \u0415\u0441\u043b\u0438 \u043a\u043b\u044e\u0447 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d, \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0438.", - "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" + "title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "\u0421\u0435\u0442\u0435\u0432\u043e\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441", "mac": "MAC-\u0430\u0434\u0440\u0435\u0441 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441\u043e \u0448\u043b\u044e\u0437\u043e\u043c Xiaomi Aqara. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0448\u043b\u044e\u0437\u0430, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u044f IP \u0438 MAC \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u043f\u0443\u0441\u0442\u044b\u043c\u0438.", + "description": "\u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0448\u043b\u044e\u0437\u0430, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u044f IP \u0438 MAC \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u043f\u0443\u0441\u0442\u044b\u043c\u0438.", "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/tr.json b/homeassistant/components/xiaomi_aqara/translations/tr.json index 6281b7a397e..0c961a34a3c 100644 --- a/homeassistant/components/xiaomi_aqara/translations/tr.json +++ b/homeassistant/components/xiaomi_aqara/translations/tr.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP Adresi" }, - "description": "Ek a\u011f ge\u00e7itlerini ba\u011flamak istiyorsan\u0131z kurulumu tekrar \u00e7al\u0131\u015ft\u0131r\u0131n.", + "description": "Ba\u011flamak istedi\u011finiz Xiaomi Aqara A\u011f Ge\u00e7idini se\u00e7in", "title": "Ba\u011flamak istedi\u011finiz Xiaomi Aqara A\u011f Ge\u00e7idini se\u00e7in" }, "settings": { @@ -27,7 +27,7 @@ "name": "A\u011f Ge\u00e7idinin Ad\u0131" }, "description": "Anahtar (parola) bu \u00f6\u011fretici kullan\u0131larak al\u0131nabilir: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Anahtar sa\u011flanmazsa, yaln\u0131zca sens\u00f6rlere eri\u015filebilir", - "title": "Xiaomi Aqara A\u011f Ge\u00e7idi, iste\u011fe ba\u011fl\u0131 ayarlar" + "title": "\u0130ste\u011fe ba\u011fl\u0131 ayarlar" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "Kullan\u0131lacak a\u011f aray\u00fcz\u00fc", "mac": "Mac Adresi (iste\u011fe ba\u011fl\u0131)" }, - "description": "Xiaomi Aqara Gateway'inize ba\u011flan\u0131n, IP ve MAC adresleri bo\u015f b\u0131rak\u0131l\u0131rsa otomatik ke\u015fif kullan\u0131l\u0131r", + "description": "IP ve MAC adresleri bo\u015f b\u0131rak\u0131l\u0131rsa otomatik ke\u015fif kullan\u0131l\u0131r", "title": "Xiaomi Aqara A\u011f Ge\u00e7idi" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json index 058efd39631..5ffe34d5d39 100644 --- a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP \u4f4d\u5740" }, - "description": "\u5982\u679c\u9084\u9700\u8981\u9023\u7dda\u81f3\u5176\u4ed6\u7db2\u95dc\uff0c\u8acb\u518d\u57f7\u884c\u4e00\u6b21\u8a2d\u5b9a", + "description": "\u5982\u679c\u6240\u8981\u9023\u7dda\u7684\u5c0f\u7c73 Aqara \u7db2\u95dc", "title": "\u9078\u64c7\u6240\u8981\u9023\u7dda\u7684\u5c0f\u7c73 Aqara \u7db2\u95dc" }, "settings": { @@ -27,7 +27,7 @@ "name": "\u7db2\u95dc\u540d\u7a31" }, "description": "\u91d1\u9470\uff08\u5bc6\u78bc\uff09\u53d6\u5f97\u8acb\u53c3\u8003\u4e0b\u65b9\u6559\u5b78\uff1ahttps://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz\u3002\u5047\u5982\u672a\u63d0\u4f9b\u91d1\u9470\u3001\u5247\u50c5\u6703\u6536\u5230\u611f\u6e2c\u5668\u88dd\u7f6e\u7684\u8cc7\u8a0a", - "title": "\u5c0f\u7c73 Aqara \u7db2\u95dc\u9078\u9805\u8a2d\u5b9a" + "title": "\u9078\u9805\u8a2d\u5b9a" }, "user": { "data": { @@ -35,7 +35,7 @@ "interface": "\u4f7f\u7528\u7684\u7db2\u8def\u4ecb\u9762", "mac": "Mac \u4f4d\u5740\uff08\u9078\u9805\uff09" }, - "description": "\u9023\u7dda\u81f3\u5c0f\u7c73 Aqara \u7db2\u95dc\uff0c\u5047\u5982 IP \u6216 Mac \u4f4d\u5740\u70ba\u7a7a\u767d\u3001\u5c07\u9032\u884c\u81ea\u52d5\u641c\u7d22", + "description": "\u5047\u5982 IP \u6216 Mac \u4f4d\u5740\u70ba\u7a7a\u767d\u3001\u5c07\u9032\u884c\u81ea\u52d5\u641c\u7d22", "title": "\u5c0f\u7c73 Aqara \u7db2\u95dc" } } diff --git a/homeassistant/components/xiaomi_miio/alarm_control_panel.py b/homeassistant/components/xiaomi_miio/alarm_control_panel.py index f7dbf60f63a..25c995b2b24 100644 --- a/homeassistant/components/xiaomi_miio/alarm_control_panel.py +++ b/homeassistant/components/xiaomi_miio/alarm_control_panel.py @@ -5,8 +5,8 @@ import logging from miio import DeviceException from homeassistant.components.alarm_control_panel import ( - SUPPORT_ALARM_ARM_AWAY, AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -49,6 +49,8 @@ async def async_setup_entry( class XiaomiGatewayAlarm(AlarmControlPanelEntity): """Representation of the XiaomiGatewayAlarm.""" + _attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY + def __init__( self, gateway_device, gateway_name, model, mac_address, gateway_device_id ): @@ -98,11 +100,6 @@ class XiaomiGatewayAlarm(AlarmControlPanelEntity): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return SUPPORT_ALARM_ARM_AWAY - async def _try_command(self, mask_error, func, *args, **kwargs): """Call a device command handling error messages.""" try: diff --git a/homeassistant/components/xiaomi_miio/diagnostics.py b/homeassistant/components/xiaomi_miio/diagnostics.py new file mode 100644 index 00000000000..eb823cd5abc --- /dev/null +++ b/homeassistant/components/xiaomi_miio/diagnostics.py @@ -0,0 +1,37 @@ +"""Diagnostics support for Xiaomi Miio.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_MAC, CONF_TOKEN, CONF_UNIQUE_ID +from homeassistant.core import HomeAssistant + +from .const import CONF_CLOUD_PASSWORD, CONF_CLOUD_USERNAME, DOMAIN, KEY_COORDINATOR + +TO_REDACT = { + CONF_CLOUD_PASSWORD, + CONF_CLOUD_USERNAME, + CONF_MAC, + CONF_TOKEN, + CONF_UNIQUE_ID, +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + diagnostics_data: dict[str, Any] = { + "config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT) + } + + # not every device uses DataUpdateCoordinator + if coordinator := hass.data[DOMAIN][config_entry.entry_id].get(KEY_COORDINATOR): + if isinstance(coordinator.data, dict): + diagnostics_data["coordinator_data"] = coordinator.data + else: + diagnostics_data["coordinator_data"] = repr(coordinator.data) + + return diagnostics_data diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 503556171f1..1e525887c56 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -17,13 +17,7 @@ from miio.integrations.fan.zhimi.zhimi_miot import ( ) import voluptuous as vol -from homeassistant.components.fan import ( - SUPPORT_DIRECTION, - SUPPORT_OSCILLATE, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID, CONF_MODEL from homeassistant.core import HomeAssistant, ServiceCall, callback @@ -289,14 +283,8 @@ class XiaomiGenericDevice(XiaomiCoordinatedMiioEntity, FanEntity): self._fan_level = None self._state_attrs = {} self._device_features = 0 - self._supported_features = 0 self._preset_modes = [] - @property - def supported_features(self): - """Flag supported features.""" - return self._supported_features - @property @abstractmethod def operation_mode_class(self): @@ -412,37 +400,39 @@ class XiaomiAirPurifier(XiaomiGenericAirPurifier): self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO - self._supported_features = SUPPORT_PRESET_MODE + self._attr_supported_features = FanEntityFeature.PRESET_MODE self._speed_count = 1 elif self._model == MODEL_AIRPURIFIER_PRO_V7: self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO_V7 self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7 self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO_V7 - self._supported_features = SUPPORT_PRESET_MODE + self._attr_supported_features = FanEntityFeature.PRESET_MODE self._speed_count = 1 elif self._model in [MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_2H]: self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON self._preset_modes = PRESET_MODES_AIRPURIFIER_2S - self._supported_features = SUPPORT_PRESET_MODE + self._attr_supported_features = FanEntityFeature.PRESET_MODE self._speed_count = 1 elif self._model in MODELS_PURIFIER_MIOT: self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT self._preset_modes = PRESET_MODES_AIRPURIFIER_MIOT - self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) self._speed_count = 3 elif self._model == MODEL_AIRPURIFIER_V3: self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3 self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 self._preset_modes = PRESET_MODES_AIRPURIFIER_V3 - self._supported_features = SUPPORT_PRESET_MODE + self._attr_supported_features = FanEntityFeature.PRESET_MODE self._speed_count = 1 else: self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIIO self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER self._preset_modes = PRESET_MODES_AIRPURIFIER - self._supported_features = SUPPORT_PRESET_MODE + self._attr_supported_features = FanEntityFeature.PRESET_MODE self._speed_count = 1 self._state = self.coordinator.data.is_on @@ -577,7 +567,7 @@ class XiaomiAirPurifierMB4(XiaomiGenericAirPurifier): self._device_features = FEATURE_FLAGS_AIRPURIFIER_3C self._preset_modes = PRESET_MODES_AIRPURIFIER_3C - self._supported_features = SUPPORT_PRESET_MODE + self._attr_supported_features = FanEntityFeature.PRESET_MODE self._state = self.coordinator.data.is_on self._mode = self.coordinator.data.mode.value @@ -633,7 +623,9 @@ class XiaomiAirFresh(XiaomiGenericAirPurifier): self._available_attributes = AVAILABLE_ATTRIBUTES_AIRFRESH self._speed_count = 4 self._preset_modes = PRESET_MODES_AIRFRESH - self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) self._state = self.coordinator.data.is_on self._state_attrs.update( @@ -727,7 +719,9 @@ class XiaomiAirFreshA1(XiaomiGenericAirPurifier): self._favorite_speed = None self._device_features = FEATURE_FLAGS_AIRFRESH_A1 self._preset_modes = PRESET_MODES_AIRFRESH_A1 - self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED | FanEntityFeature.PRESET_MODE + ) self._state = self.coordinator.data.is_on self._mode = self.coordinator.data.mode.value @@ -820,11 +814,13 @@ class XiaomiGenericFan(XiaomiGenericDevice): self._device_features = FEATURE_FLAGS_FAN_P10_P11 else: self._device_features = FEATURE_FLAGS_FAN - self._supported_features = ( - SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_PRESET_MODE + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.OSCILLATE + | FanEntityFeature.PRESET_MODE ) if self._model != MODEL_FAN_1C: - self._supported_features |= SUPPORT_DIRECTION + self._attr_supported_features |= FanEntityFeature.DIRECTION self._preset_mode = None self._oscillating = None self._percentage = None diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index 27f426ff9d6..ebfb37ab8fb 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -81,7 +81,7 @@ class ConnectXiaomiGateway: raise AuthException(error) from error raise SetupException( - "DeviceException during setup of xiaomi gateway with host {self._host}" + f"DeviceException during setup of xiaomi gateway with host {self._host}" ) from error # get the connected sub devices @@ -115,7 +115,7 @@ class ConnectXiaomiGateway: if not miio_cloud.login(): raise SetupException( "Failed to login to Xiaomi Miio Cloud during setup of Xiaomi" - " gateway with host {self._host}", + f" gateway with host {self._host}", ) devices_raw = miio_cloud.get_devices(self._cloud_country) self._gateway_device.get_devices_from_dict(devices_raw) diff --git a/homeassistant/components/xiaomi_miio/humidifier.py b/homeassistant/components/xiaomi_miio/humidifier.py index 9a9fe43b739..d5c829754d6 100644 --- a/homeassistant/components/xiaomi_miio/humidifier.py +++ b/homeassistant/components/xiaomi_miio/humidifier.py @@ -6,8 +6,11 @@ from miio.airhumidifier import OperationMode as AirhumidifierOperationMode from miio.airhumidifier_miot import OperationMode as AirhumidifierMiotOperationMode from miio.airhumidifier_mjjsq import OperationMode as AirhumidifierMjjsqOperationMode -from homeassistant.components.humidifier import HumidifierDeviceClass, HumidifierEntity -from homeassistant.components.humidifier.const import SUPPORT_MODES +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_MODE, CONF_MODEL from homeassistant.core import HomeAssistant, callback @@ -108,7 +111,7 @@ class XiaomiGenericHumidifier(XiaomiCoordinatedMiioEntity, HumidifierEntity): """Representation of a generic Xiaomi humidifier device.""" _attr_device_class = HumidifierDeviceClass.HUMIDIFIER - _attr_supported_features = SUPPORT_MODES + _attr_supported_features = HumidifierEntityFeature.MODES def __init__(self, name, device, entry, unique_id, coordinator): """Initialize the generic Xiaomi device.""" @@ -243,7 +246,7 @@ class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity): ): self._target_humidity = target_humidity if ( - self.supported_features & SUPPORT_MODES == 0 + self.supported_features & HumidifierEntityFeature.MODES == 0 or AirhumidifierOperationMode(self._attributes[ATTR_MODE]) == AirhumidifierOperationMode.Auto or AirhumidifierOperationMode.Auto.name not in self.available_modes @@ -261,7 +264,7 @@ class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity): async def async_set_mode(self, mode: str) -> None: """Set the mode of the humidifier.""" - if self.supported_features & SUPPORT_MODES == 0 or not mode: + if self.supported_features & HumidifierEntityFeature.MODES == 0 or not mode: return if mode not in self.available_modes: @@ -321,7 +324,7 @@ class XiaomiAirHumidifierMiot(XiaomiAirHumidifier): ): self._target_humidity = target_humidity if ( - self.supported_features & SUPPORT_MODES == 0 + self.supported_features & HumidifierEntityFeature.MODES == 0 or AirhumidifierMiotOperationMode(self._attributes[ATTR_MODE]) == AirhumidifierMiotOperationMode.Auto ): @@ -338,7 +341,7 @@ class XiaomiAirHumidifierMiot(XiaomiAirHumidifier): async def async_set_mode(self, mode: str) -> None: """Set the mode of the fan.""" - if self.supported_features & SUPPORT_MODES == 0 or not mode: + if self.supported_features & HumidifierEntityFeature.MODES == 0 or not mode: return if mode not in self.REVERSE_MODE_MAPPING: @@ -396,7 +399,7 @@ class XiaomiAirHumidifierMjjsq(XiaomiAirHumidifier): ): self._target_humidity = target_humidity if ( - self.supported_features & SUPPORT_MODES == 0 + self.supported_features & HumidifierEntityFeature.MODES == 0 or AirhumidifierMjjsqOperationMode(self._attributes[ATTR_MODE]) == AirhumidifierMjjsqOperationMode.Humidity ): diff --git a/homeassistant/components/xiaomi_miio/translations/he.json b/homeassistant/components/xiaomi_miio/translations/he.json index 4bb0251d6cb..db0cee16884 100644 --- a/homeassistant/components/xiaomi_miio/translations/he.json +++ b/homeassistant/components/xiaomi_miio/translations/he.json @@ -51,7 +51,7 @@ "name": "\u05e9\u05dd \u05d4\u05e9\u05e2\u05e8", "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API" }, - "description": "\u05d0\u05ea\u05d4 \u05d6\u05e7\u05d5\u05e7 \u05dc-32 \u05ea\u05d5\u05d5\u05d9 \u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05df API , \u05e8\u05d0\u05d4 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u05dc\u05d4\u05d5\u05e8\u05d0\u05d5\u05ea. \u05e9\u05d9\u05dd \u05dc\u05d1, \u05db\u05d9 \u05d0\u05e1\u05d9\u05de\u05d5\u05df API \u05e9\u05d5\u05e0\u05d4 \u05de\u05d4\u05de\u05e4\u05ea\u05d7 \u05d4\u05de\u05e9\u05de\u05e9 \u05d0\u05ea \u05e9\u05d9\u05dc\u05d5\u05d1 Xiaomi Aqara.", + "description": "\u05e0\u05d3\u05e8\u05e9\u05d9\u05dd 32 \u05ea\u05d5\u05d5\u05d9 \u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05df API, \u05e8\u05d0\u05d4 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u05dc\u05d4\u05d5\u05e8\u05d0\u05d5\u05ea. \u05e9\u05d9\u05dd \u05dc\u05d1, \u05db\u05d9 \u05d0\u05e1\u05d9\u05de\u05d5\u05df API \u05e9\u05d5\u05e0\u05d4 \u05de\u05d4\u05de\u05e4\u05ea\u05d7 \u05d4\u05de\u05e9\u05de\u05e9 \u05d0\u05ea \u05e9\u05d9\u05dc\u05d5\u05d1 Xiaomi Aqara.", "title": "\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05e9\u05e2\u05e8 \u05e9\u05d9\u05d0\u05d5\u05de\u05d9" }, "manual": { diff --git a/homeassistant/components/xiaomi_miio/translations/hu.json b/homeassistant/components/xiaomi_miio/translations/hu.json index 8d2340988dd..189e9906e24 100644 --- a/homeassistant/components/xiaomi_miio/translations/hu.json +++ b/homeassistant/components/xiaomi_miio/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "incomplete_info": "Az eszk\u00f6z be\u00e1ll\u00edt\u00e1s\u00e1hoz sz\u00fcks\u00e9ges inform\u00e1ci\u00f3k hi\u00e1nyosak, nincs megadva \u00e1llom\u00e1s vagy token.", "not_xiaomi_miio": "Az eszk\u00f6zt (m\u00e9g) nem t\u00e1mogatja a Xiaomi Miio integr\u00e1ci\u00f3.", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt." diff --git a/homeassistant/components/xiaomi_miio/translations/pt-BR.json b/homeassistant/components/xiaomi_miio/translations/pt-BR.json index 9e966a541d5..1116de258fa 100644 --- a/homeassistant/components/xiaomi_miio/translations/pt-BR.json +++ b/homeassistant/components/xiaomi_miio/translations/pt-BR.json @@ -25,7 +25,7 @@ "cloud_username": "Usu\u00e1rio da Cloud", "manual": "Configurar manualmente (n\u00e3o recomendado)" }, - "description": "Fa\u00e7a login na Cloud Xiaomi Miio, consulte https://www.openhab.org/addons/bindings/miio/#country-servers para o servidor em cloud usar.", + "description": "Coloque o login da Cloud Xiaomi Miio e consulte https://www.openhab.org/addons/bindings/miio/#country-servers para saber qual servidor cloud usar.", "title": "Conecte-se a um dispositivo Xiaomi Miio ou Xiaomi Gateway" }, "connect": { diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 555a352a6ce..6d398ff40b9 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -14,17 +14,8 @@ from homeassistant.components.vacuum import ( STATE_IDLE, STATE_PAUSED, STATE_RETURNING, - SUPPORT_BATTERY, - SUPPORT_CLEAN_SPOT, - SUPPORT_FAN_SPEED, - SUPPORT_LOCATE, - SUPPORT_PAUSE, - SUPPORT_RETURN_HOME, - SUPPORT_SEND_COMMAND, - SUPPORT_START, - SUPPORT_STATE, - SUPPORT_STOP, StateVacuumEntity, + VacuumEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -61,20 +52,6 @@ ATTR_ZONE_ARRAY = "zone" ATTR_ZONE_REPEATER = "repeats" ATTR_TIMERS = "timers" -SUPPORT_XIAOMI = ( - SUPPORT_STATE - | SUPPORT_PAUSE - | SUPPORT_STOP - | SUPPORT_RETURN_HOME - | SUPPORT_FAN_SPEED - | SUPPORT_SEND_COMMAND - | SUPPORT_LOCATE - | SUPPORT_BATTERY - | SUPPORT_CLEAN_SPOT - | SUPPORT_START -) - - STATE_CODE_TO_STATE = { 1: STATE_IDLE, # "Starting" 2: STATE_IDLE, # "Charger disconnected" @@ -209,6 +186,19 @@ class MiroboVacuum( ): """Representation of a Xiaomi Vacuum cleaner robot.""" + _attr_supported_features = ( + VacuumEntityFeature.STATE + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.STOP + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.SEND_COMMAND + | VacuumEntityFeature.LOCATE + | VacuumEntityFeature.BATTERY + | VacuumEntityFeature.CLEAN_SPOT + | VacuumEntityFeature.START + ) + def __init__( self, name, @@ -237,12 +227,12 @@ class MiroboVacuum( return self._state @property - def battery_level(self): + def battery_level(self) -> int: """Return the battery level of the vacuum cleaner.""" return self.coordinator.data.status.battery @property - def fan_speed(self): + def fan_speed(self) -> str: """Return the fan speed of the vacuum cleaner.""" speed = self.coordinator.data.status.fanspeed if speed in self.coordinator.data.fan_speeds_reverse: @@ -250,16 +240,14 @@ class MiroboVacuum( _LOGGER.debug("Unable to find reverse for %s", speed) - return speed + return str(speed) @property - def fan_speed_list(self): + def fan_speed_list(self) -> list[str]: """Get the list of available fan speed steps of the vacuum cleaner.""" - return ( - list(self.coordinator.data.fan_speeds) - if self.coordinator.data.fan_speeds - else [] - ) + if speed_list := self.coordinator.data.fan_speeds: + return list(speed_list) + return [] @property def timers(self): @@ -286,11 +274,6 @@ class MiroboVacuum( attrs[ATTR_TIMERS] = self.timers return attrs - @property - def supported_features(self): - """Flag vacuum cleaner robot features that are supported.""" - return SUPPORT_XIAOMI - async def _try_command(self, mask_error, func, *args, **kwargs): """Call a vacuum command handling error messages.""" try: diff --git a/homeassistant/components/xiaomi_tv/media_player.py b/homeassistant/components/xiaomi_tv/media_player.py index 095eee571e5..a808d1e3b8e 100644 --- a/homeassistant/components/xiaomi_tv/media_player.py +++ b/homeassistant/components/xiaomi_tv/media_player.py @@ -6,11 +6,10 @@ import logging import pymitv import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_STEP, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant @@ -22,8 +21,6 @@ DEFAULT_NAME = "Xiaomi TV" _LOGGER = logging.getLogger(__name__) -SUPPORT_XIAOMI_TV = SUPPORT_VOLUME_STEP | SUPPORT_TURN_ON | SUPPORT_TURN_OFF - # No host is needed for configuration, however it can be set. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -60,6 +57,12 @@ def setup_platform( class XiaomiTV(MediaPlayerEntity): """Represent the Xiaomi TV for Home Assistant.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + ) + def __init__(self, ip, name): """Receive IP address and name to construct class.""" @@ -84,11 +87,6 @@ class XiaomiTV(MediaPlayerEntity): """Indicate that state is assumed.""" return True - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_XIAOMI_TV - def turn_off(self): """ Instruct the TV to turn sleep. diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index bef95faf1b2..6b4faf32458 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -347,7 +347,6 @@ async def async_send_message( # noqa: C901 except NotConnectedError as ex: _LOGGER.error("Connection error %s", ex) - # pylint: disable=no-self-use def get_random_filename(self, filename, extension=None): """Return a random filename, leaving the extension intact.""" if extension is None: diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py index c05ce7e24f6..bf2ab898112 100644 --- a/homeassistant/components/xs1/climate.py +++ b/homeassistant/components/xs1/climate.py @@ -4,10 +4,7 @@ from __future__ import annotations from xs1_api_client.api_constants import ActuatorType from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -18,8 +15,6 @@ from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity MIN_TEMP = 8 MAX_TEMP = 25 -SUPPORT_HVAC = [HVAC_MODE_HEAT] - def setup_platform( hass: HomeAssistant, @@ -51,6 +46,10 @@ def setup_platform( class XS1ThermostatEntity(XS1DeviceEntity, ClimateEntity): """Representation of a XS1 thermostat.""" + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def __init__(self, device, sensor): """Initialize the actuator.""" super().__init__(device) @@ -61,27 +60,6 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateEntity): """Return the name of the device if any.""" return self.device.name() - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_TARGET_TEMPERATURE - - @property - def hvac_mode(self): - """Return hvac operation ie. heat, cool mode. - - Need to be one of HVAC_MODE_*. - """ - return HVAC_MODE_HEAT - - @property - def hvac_modes(self): - """Return the list of available hvac operation modes. - - Need to be a subset of HVAC_MODES. - """ - return SUPPORT_HVAC - @property def current_temperature(self): """Return the current temperature.""" @@ -119,7 +97,7 @@ class XS1ThermostatEntity(XS1DeviceEntity, ClimateEntity): if self.sensor is not None: self.schedule_update_ha_state() - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" async def async_update(self): diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py index 20d88ca4859..fbd3f945aa2 100644 --- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py +++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py @@ -9,10 +9,9 @@ from yalesmartalarmclient.const import ( YALE_STATE_DISARM, ) -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME @@ -40,7 +39,10 @@ class YaleAlarmDevice(YaleAlarmEntity, AlarmControlPanelEntity): """Represent a Yale Smart Alarm.""" _attr_code_arm_required = False - _attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + ) def __init__(self, coordinator: YaleDataUpdateCoordinator) -> None: """Initialize the Yale Alarm Device.""" diff --git a/homeassistant/components/yale_smart_alarm/button.py b/homeassistant/components/yale_smart_alarm/button.py new file mode 100644 index 00000000000..081c25c4342 --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/button.py @@ -0,0 +1,60 @@ +"""Support for Yale Smart Alarm button.""" +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import COORDINATOR, DOMAIN +from .coordinator import YaleDataUpdateCoordinator +from .entity import YaleAlarmEntity + +BUTTON_TYPES = ( + ButtonEntityDescription(key="panic", name="Panic Button", icon="mdi:alarm-light"), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the button from a config entry.""" + + coordinator: YaleDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + COORDINATOR + ] + + async_add_entities( + [YalePanicButton(coordinator, description) for description in BUTTON_TYPES] + ) + + +class YalePanicButton(YaleAlarmEntity, ButtonEntity): + """A Panic button for Yale Smart Alarm.""" + + entity_description: ButtonEntityDescription + + def __init__( + self, + coordinator: YaleDataUpdateCoordinator, + description: ButtonEntityDescription, + ) -> None: + """Initialize the plug switch.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_name = f"{coordinator.entry.data[CONF_NAME]} {description.name}" + self._attr_unique_id = f"yale_smart_alarm-{description.key}" + + async def async_press(self, **kwargs: Any) -> None: + """Press the button.""" + if TYPE_CHECKING: + assert self.coordinator.yale, "Connection to API is missing" + + await self.hass.async_add_executor_job( + self.coordinator.yale.trigger_panic_button + ) diff --git a/homeassistant/components/yale_smart_alarm/const.py b/homeassistant/components/yale_smart_alarm/const.py index e506a2c70d6..71b34a3011a 100644 --- a/homeassistant/components/yale_smart_alarm/const.py +++ b/homeassistant/components/yale_smart_alarm/const.py @@ -34,7 +34,12 @@ LOGGER = logging.getLogger(__package__) ATTR_ONLINE = "online" ATTR_STATUS = "status" -PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR, Platform.LOCK] +PLATFORMS = [ + Platform.ALARM_CONTROL_PANEL, + Platform.BINARY_SENSOR, + Platform.BUTTON, + Platform.LOCK, +] STATE_MAP = { YALE_STATE_DISARM: STATE_ALARM_DISARMED, diff --git a/homeassistant/components/yale_smart_alarm/translations/hu.json b/homeassistant/components/yale_smart_alarm/translations/hu.json index 028f2cd6f0f..a18480fcac0 100644 --- a/homeassistant/components/yale_smart_alarm/translations/hu.json +++ b/homeassistant/components/yale_smart_alarm/translations/hu.json @@ -12,7 +12,7 @@ "reauth_confirm": { "data": { "area_id": "Ter\u00fclet ID", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } @@ -20,7 +20,7 @@ "user": { "data": { "area_id": "Ter\u00fclet ID", - "name": "N\u00e9v", + "name": "Elnevez\u00e9s", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/yamaha/media_player.py b/homeassistant/components/yamaha/media_player.py index 4a816b99aca..1e48fceba07 100644 --- a/homeassistant/components/yamaha/media_player.py +++ b/homeassistant/components/yamaha/media_player.py @@ -7,22 +7,12 @@ import requests import rxv import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - MEDIA_TYPE_MUSIC, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -73,13 +63,13 @@ DATA_YAMAHA = "yamaha_known_receivers" DEFAULT_NAME = "Yamaha Receiver" SUPPORT_YAMAHA = ( - SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY - | SUPPORT_SELECT_SOUND_MODE + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.SELECT_SOUND_MODE ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -332,11 +322,13 @@ class YamahaDevice(MediaPlayerEntity): supports = self._playback_support mapping = { - "play": (SUPPORT_PLAY | SUPPORT_PLAY_MEDIA), - "pause": SUPPORT_PAUSE, - "stop": SUPPORT_STOP, - "skip_f": SUPPORT_NEXT_TRACK, - "skip_r": SUPPORT_PREVIOUS_TRACK, + "play": ( + MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PLAY_MEDIA + ), + "pause": MediaPlayerEntityFeature.PAUSE, + "stop": MediaPlayerEntityFeature.STOP, + "skip_f": MediaPlayerEntityFeature.NEXT_TRACK, + "skip_r": MediaPlayerEntityFeature.PREVIOUS_TRACK, } for attr, feature in mapping.items(): if getattr(supports, attr, False): diff --git a/homeassistant/components/yamaha_musiccast/__init__.py b/homeassistant/components/yamaha_musiccast/__init__.py index e28712cdf21..a3362e0558a 100644 --- a/homeassistant/components/yamaha_musiccast/__init__.py +++ b/homeassistant/components/yamaha_musiccast/__init__.py @@ -30,7 +30,7 @@ from .const import ( ENTITY_CATEGORY_MAPPING, ) -PLATFORMS = [Platform.MEDIA_PLAYER, Platform.NUMBER, Platform.SELECT] +PLATFORMS = [Platform.MEDIA_PLAYER, Platform.NUMBER, Platform.SELECT, Platform.SWITCH] _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=60) diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index 3618bc78dbe..d0141977f29 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -8,7 +8,11 @@ from aiomusiccast import MusicCastGroupException, MusicCastMediaContent from aiomusiccast.features import ZoneFeature from homeassistant.components import media_source -from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity +from homeassistant.components.media_player import ( + BrowseMedia, + MediaPlayerEntity, + MediaPlayerEntityFeature, +) from homeassistant.components.media_player.browse_media import ( async_process_play_media_url, ) @@ -17,23 +21,6 @@ from homeassistant.components.media_player.const import ( MEDIA_CLASS_TRACK, MEDIA_TYPE_MUSIC, REPEAT_MODE_OFF, - SUPPORT_BROWSE_MEDIA, - SUPPORT_GROUPING, - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PLAY_MEDIA, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_REPEAT_SET, - SUPPORT_SELECT_SOUND_MODE, - SUPPORT_SELECT_SOURCE, - SUPPORT_SHUFFLE_SET, - SUPPORT_STOP, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, - SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING @@ -58,12 +45,12 @@ from .const import ( _LOGGER = logging.getLogger(__name__) MUSIC_PLAYER_BASE_SUPPORT = ( - SUPPORT_SHUFFLE_SET - | SUPPORT_REPEAT_SET - | SUPPORT_SELECT_SOUND_MODE - | SUPPORT_SELECT_SOURCE - | SUPPORT_GROUPING - | SUPPORT_PLAY_MEDIA + MediaPlayerEntityFeature.SHUFFLE_SET + | MediaPlayerEntityFeature.REPEAT_SET + | MediaPlayerEntityFeature.SELECT_SOUND_MODE + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.GROUPING + | MediaPlayerEntityFeature.PLAY_MEDIA ) @@ -449,23 +436,28 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity): zone = self.coordinator.data.zones[self._zone_id] if ZoneFeature.POWER in zone.features: - supported_features |= SUPPORT_TURN_ON | SUPPORT_TURN_OFF + supported_features |= ( + MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF + ) if ZoneFeature.VOLUME in zone.features: - supported_features |= SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP + supported_features |= ( + MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + ) if ZoneFeature.MUTE in zone.features: - supported_features |= SUPPORT_VOLUME_MUTE + supported_features |= MediaPlayerEntityFeature.VOLUME_MUTE if self._is_netusb or self._is_tuner: - supported_features |= SUPPORT_PREVIOUS_TRACK - supported_features |= SUPPORT_NEXT_TRACK + supported_features |= MediaPlayerEntityFeature.PREVIOUS_TRACK + supported_features |= MediaPlayerEntityFeature.NEXT_TRACK if self._is_netusb: - supported_features |= SUPPORT_PAUSE - supported_features |= SUPPORT_PLAY - supported_features |= SUPPORT_STOP + supported_features |= MediaPlayerEntityFeature.PAUSE + supported_features |= MediaPlayerEntityFeature.PLAY + supported_features |= MediaPlayerEntityFeature.STOP if self.state != STATE_OFF: - supported_features |= SUPPORT_BROWSE_MEDIA + supported_features |= MediaPlayerEntityFeature.BROWSE_MEDIA return supported_features diff --git a/homeassistant/components/yamaha_musiccast/switch.py b/homeassistant/components/yamaha_musiccast/switch.py new file mode 100644 index 00000000000..f48e7c11713 --- /dev/null +++ b/homeassistant/components/yamaha_musiccast/switch.py @@ -0,0 +1,52 @@ +"""The switch entities for musiccast.""" +from typing import Any + +from aiomusiccast.capabilities import BinarySetter + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import DOMAIN, MusicCastCapabilityEntity, MusicCastDataUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up MusicCast sensor based on a config entry.""" + coordinator: MusicCastDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + switch_entities = [] + + for capability in coordinator.data.capabilities: + if isinstance(capability, BinarySetter): + switch_entities.append(SwitchCapability(coordinator, capability)) + + for zone, data in coordinator.data.zones.items(): + for capability in data.capabilities: + if isinstance(capability, BinarySetter): + switch_entities.append(SwitchCapability(coordinator, capability, zone)) + + async_add_entities(switch_entities) + + +class SwitchCapability(MusicCastCapabilityEntity, SwitchEntity): + """Representation of a MusicCast switch entity.""" + + capability: BinarySetter + + @property + def is_on(self) -> bool: + """Return the current status.""" + return self.capability.current + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the capability.""" + await self.capability.set(True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the capability.""" + await self.capability.set(False) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index 0bca1267565..4fd742e24f5 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -8,7 +8,7 @@ import voluptuous as vol from yeelight import BulbException from yeelight.aio import AsyncBulb -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryNotReady +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_DEVICES, CONF_HOST, @@ -18,6 +18,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 1de97c45fd0..a368490e51e 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -21,18 +21,11 @@ from homeassistant.components.light import ( ATTR_KELVIN, ATTR_RGB_COLOR, ATTR_TRANSITION, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_ONOFF, - COLOR_MODE_RGB, - COLOR_MODE_UNKNOWN, FLASH_LONG, FLASH_SHORT, - SUPPORT_EFFECT, - SUPPORT_FLASH, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, CONF_NAME @@ -73,8 +66,6 @@ from .entity import YeelightEntity _LOGGER = logging.getLogger(__name__) -SUPPORT_YEELIGHT = SUPPORT_TRANSITION | SUPPORT_FLASH | SUPPORT_EFFECT - ATTR_MINUTES = "minutes" SERVICE_SET_MODE = "set_mode" @@ -253,25 +244,25 @@ def _async_cmd(func): except asyncio.TimeoutError as ex: # The wifi likely dropped, so we want to retry once since # python-yeelight will auto reconnect - exc_message = str(ex) or type(ex) if attempts == 0: continue raise HomeAssistantError( - f"Timed out when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}" + f"Timed out when calling {func.__name__} for bulb " + f"{self.device.name} at {self.device.host}: {str(ex) or type(ex)}" ) from ex except OSError as ex: # A network error happened, the bulb is likely offline now self.device.async_mark_unavailable() self.async_state_changed() - exc_message = str(ex) or type(ex) raise HomeAssistantError( - f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}" + f"Error when calling {func.__name__} for bulb " + f"{self.device.name} at {self.device.host}: {str(ex) or type(ex)}" ) from ex except BulbException as ex: # The bulb likely responded but had an error - exc_message = str(ex) or type(ex) raise HomeAssistantError( - f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}" + f"Error when calling {func.__name__} for bulb " + f"{self.device.name} at {self.device.host}: {str(ex) or type(ex)}" ) from ex return _async_wrap @@ -413,8 +404,13 @@ def _async_setup_services(hass: HomeAssistant): class YeelightGenericLight(YeelightEntity, LightEntity): """Representation of a Yeelight generic light.""" - _attr_color_mode = COLOR_MODE_BRIGHTNESS - _attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = ( + LightEntityFeature.TRANSITION + | LightEntityFeature.FLASH + | LightEntityFeature.EFFECT + ) _attr_should_poll = False def __init__(self, device, entry, custom_effects=None): @@ -457,11 +453,6 @@ class YeelightGenericLight(YeelightEntity, LightEntity): ) await super().async_added_to_hass() - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_YEELIGHT - @property def effect_list(self): """Return the list of supported effects.""" @@ -522,7 +513,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): return self._light_type @property - def hs_color(self) -> tuple: + def hs_color(self) -> tuple[int, int] | None: """Return the color property.""" hue = self._get_property("hue") sat = self._get_property("sat") @@ -532,7 +523,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): return (int(hue), int(sat)) @property - def rgb_color(self) -> tuple: + def rgb_color(self) -> tuple[int, int, int] | None: """Return the color property.""" if (rgb := self._get_property("rgb")) is None: return None @@ -637,11 +628,15 @@ class YeelightGenericLight(YeelightEntity, LightEntity): @_async_cmd async def async_set_hs(self, hs_color, duration) -> None: """Set bulb's color.""" - if not hs_color or COLOR_MODE_HS not in self.supported_color_modes: + if ( + not hs_color + or not self.supported_color_modes + or ColorMode.HS not in self.supported_color_modes + ): return if ( not self.device.is_color_flow_enabled - and self.color_mode == COLOR_MODE_HS + and self.color_mode == ColorMode.HS and self.hs_color == hs_color ): _LOGGER.debug("HS already set to: %s", hs_color) @@ -658,11 +653,15 @@ class YeelightGenericLight(YeelightEntity, LightEntity): @_async_cmd async def async_set_rgb(self, rgb, duration) -> None: """Set bulb's color.""" - if not rgb or COLOR_MODE_RGB not in self.supported_color_modes: + if ( + not rgb + or not self.supported_color_modes + or ColorMode.RGB not in self.supported_color_modes + ): return if ( not self.device.is_color_flow_enabled - and self.color_mode == COLOR_MODE_RGB + and self.color_mode == ColorMode.RGB and self.rgb_color == rgb ): _LOGGER.debug("RGB already set to: %s", rgb) @@ -679,13 +678,17 @@ class YeelightGenericLight(YeelightEntity, LightEntity): @_async_cmd async def async_set_colortemp(self, colortemp, duration) -> None: """Set bulb's color temperature.""" - if not colortemp or COLOR_MODE_COLOR_TEMP not in self.supported_color_modes: + if ( + not colortemp + or not self.supported_color_modes + or ColorMode.COLOR_TEMP not in self.supported_color_modes + ): return temp_in_k = mired_to_kelvin(colortemp) if ( not self.device.is_color_flow_enabled - and self.color_mode == COLOR_MODE_COLOR_TEMP + and self.color_mode == ColorMode.COLOR_TEMP and self.color_temp == colortemp ): _LOGGER.debug("Color temp already set to: %s", temp_in_k) @@ -708,7 +711,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): """Activate flash.""" if not flash: return - if int(self._get_property("color_mode")) != 1: + if int(self._get_property("color_mode")) != 1 or not self.hs_color: _LOGGER.error("Flash supported currently only in RGB mode") return @@ -782,7 +785,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): duration = int(self.config[CONF_TRANSITION]) # in ms if ATTR_TRANSITION in kwargs: # passed kwarg overrides config - duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s + duration = int(kwargs[ATTR_TRANSITION] * 1000) # kwarg in s if not self.is_on: await self._async_turn_on(duration) @@ -840,7 +843,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): duration = int(self.config[CONF_TRANSITION]) # in ms if ATTR_TRANSITION in kwargs: # passed kwarg overrides config - duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s + duration = int(kwargs[ATTR_TRANSITION] * 1000) # kwarg in s await self._async_turn_off(duration) self._async_schedule_state_check(False) @@ -870,31 +873,31 @@ class YeelightGenericLight(YeelightEntity, LightEntity): class YeelightColorLightSupport(YeelightGenericLight): """Representation of a Color Yeelight light support.""" - _attr_supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS, COLOR_MODE_RGB} + _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS, ColorMode.RGB} @property - def color_mode(self): + def color_mode(self) -> ColorMode: """Return the color mode.""" color_mode = int(self._get_property("color_mode")) if color_mode == 1: # RGB - return COLOR_MODE_RGB + return ColorMode.RGB if color_mode == 2: # color temperature - return COLOR_MODE_COLOR_TEMP + return ColorMode.COLOR_TEMP if color_mode == 3: # hsv - return COLOR_MODE_HS + return ColorMode.HS _LOGGER.debug("Light reported unknown color mode: %s", color_mode) - return COLOR_MODE_UNKNOWN + return ColorMode.UNKNOWN @property def _predefined_effects(self): return YEELIGHT_COLOR_EFFECT_LIST -class YeelightWhiteTempLightSupport: +class YeelightWhiteTempLightSupport(YeelightGenericLight): """Representation of a White temp Yeelight light.""" - _attr_color_mode = COLOR_MODE_COLOR_TEMP - _attr_supported_color_modes = {COLOR_MODE_COLOR_TEMP} + _attr_color_mode = ColorMode.COLOR_TEMP + _attr_supported_color_modes = {ColorMode.COLOR_TEMP} @property def _predefined_effects(self): @@ -909,7 +912,7 @@ class YeelightNightLightSupport: return PowerMode.NORMAL -class YeelightWithoutNightlightSwitchMixIn: +class YeelightWithoutNightlightSwitchMixIn(YeelightGenericLight): """A mix-in for yeelights without a nightlight switch.""" @property @@ -931,9 +934,7 @@ class YeelightWithoutNightlightSwitchMixIn: class YeelightColorLightWithoutNightlightSwitch( - YeelightColorLightSupport, - YeelightWithoutNightlightSwitchMixIn, - YeelightGenericLight, + YeelightColorLightSupport, YeelightWithoutNightlightSwitchMixIn ): """Representation of a Color Yeelight light.""" @@ -953,9 +954,7 @@ class YeelightColorLightWithNightlightSwitch( class YeelightWhiteTempWithoutNightlightSwitch( - YeelightWhiteTempLightSupport, - YeelightWithoutNightlightSwitchMixIn, - YeelightGenericLight, + YeelightWhiteTempLightSupport, YeelightWithoutNightlightSwitchMixIn ): """White temp light, when nightlight switch is not set to light.""" @@ -977,8 +976,8 @@ class YeelightWithNightLight( class YeelightNightLightMode(YeelightGenericLight): """Representation of a Yeelight when in nightlight mode.""" - _attr_color_mode = COLOR_MODE_BRIGHTNESS - _attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @property def unique_id(self) -> str: @@ -1029,8 +1028,8 @@ class YeelightNightLightModeWithoutBrightnessControl(YeelightNightLightMode): It represents case when nightlight mode brightness control is not supported. """ - _attr_color_mode = COLOR_MODE_ONOFF - _attr_supported_color_modes = {COLOR_MODE_ONOFF} + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} class YeelightWithAmbientWithoutNightlight(YeelightWhiteTempWithoutNightlightSwitch): diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 1967793b855..3df38a41995 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,7 +2,7 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.7.10", "async-upnp-client==0.27.0"], + "requirements": ["yeelight==0.7.10", "async-upnp-client==0.29.0"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], diff --git a/homeassistant/components/yeelight/scanner.py b/homeassistant/components/yeelight/scanner.py index 6876b93a0eb..088071244b3 100644 --- a/homeassistant/components/yeelight/scanner.py +++ b/homeassistant/components/yeelight/scanner.py @@ -2,16 +2,19 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable, Callable, ValuesView import contextlib -from ipaddress import IPv4Address, IPv6Address +from datetime import datetime +from ipaddress import IPv4Address import logging from urllib.parse import urlparse -from async_upnp_client.search import SsdpHeaders, SsdpSearchListener +from async_upnp_client.search import SsdpSearchListener +from async_upnp_client.utils import CaseInsensitiveDict from homeassistant import config_entries from homeassistant.components import network, ssdp -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.event import async_call_later, async_track_time_interval from .const import ( @@ -34,7 +37,7 @@ class YeelightScanner: @classmethod @callback - def async_get(cls, hass: HomeAssistant): + def async_get(cls, hass: HomeAssistant) -> YeelightScanner: """Get scanner instance.""" if cls._scanner is None: cls._scanner = cls(hass) @@ -43,14 +46,14 @@ class YeelightScanner: def __init__(self, hass: HomeAssistant) -> None: """Initialize class.""" self._hass = hass - self._host_discovered_events = {} - self._unique_id_capabilities = {} - self._host_capabilities = {} - self._track_interval = None - self._listeners = [] - self._connected_events = [] + self._host_discovered_events: dict[str, list[asyncio.Event]] = {} + self._unique_id_capabilities: dict[str, CaseInsensitiveDict] = {} + self._host_capabilities: dict[str, CaseInsensitiveDict] = {} + self._track_interval: CALLBACK_TYPE | None = None + self._listeners: list[SsdpSearchListener] = [] + self._connected_events: list[asyncio.Event] = [] - async def async_setup(self): + async def async_setup(self) -> None: """Set up the scanner.""" if self._connected_events: await self._async_wait_connected() @@ -59,10 +62,10 @@ class YeelightScanner: for idx, source_ip in enumerate(await self._async_build_source_set()): self._connected_events.append(asyncio.Event()) - def _wrap_async_connected_idx(idx): + def _wrap_async_connected_idx(idx) -> Callable[[], Awaitable[None]]: """Create a function to capture the idx cell variable.""" - async def _async_connected(): + async def _async_connected() -> None: self._connected_events[idx].set() return _async_connected @@ -118,10 +121,10 @@ class YeelightScanner: return { source_ip for source_ip in await network.async_get_enabled_source_ips(self._hass) - if not source_ip.is_loopback and not isinstance(source_ip, IPv6Address) + if isinstance(source_ip, IPv4Address) and not source_ip.is_loopback } - async def async_discover(self): + async def async_discover(self) -> ValuesView[CaseInsensitiveDict]: """Discover bulbs.""" _LOGGER.debug("Yeelight discover with interval %s", DISCOVERY_SEARCH_INTERVAL) await self.async_setup() @@ -131,13 +134,13 @@ class YeelightScanner: return self._unique_id_capabilities.values() @callback - def async_scan(self, *_): + def async_scan(self, _: datetime | None = None) -> None: """Send discovery packets.""" _LOGGER.debug("Yeelight scanning") for listener in self._listeners: listener.async_search() - async def async_get_capabilities(self, host): + async def async_get_capabilities(self, host: str) -> CaseInsensitiveDict | None: """Get capabilities via SSDP.""" if host in self._host_capabilities: return self._host_capabilities[host] @@ -155,9 +158,9 @@ class YeelightScanner: self._host_discovered_events[host].remove(host_event) return self._host_capabilities.get(host) - def _async_discovered_by_ssdp(self, response): + def _async_discovered_by_ssdp(self, response: CaseInsensitiveDict) -> None: @callback - def _async_start_flow(*_): + def _async_start_flow(*_) -> None: asyncio.create_task( self._hass.config_entries.flow.async_init( DOMAIN, @@ -175,11 +178,12 @@ class YeelightScanner: # of another discovery async_call_later(self._hass, 1, _async_start_flow) - async def _async_process_entry(self, headers: SsdpHeaders): + async def _async_process_entry(self, headers: CaseInsensitiveDict) -> None: """Process a discovery.""" _LOGGER.debug("Discovered via SSDP: %s", headers) unique_id = headers["id"] host = urlparse(headers["location"]).hostname + assert host current_entry = self._unique_id_capabilities.get(unique_id) # Make sure we handle ip changes if not current_entry or host != urlparse(current_entry["location"]).hostname: diff --git a/homeassistant/components/yeelight/translations/cs.json b/homeassistant/components/yeelight/translations/cs.json index adc42efddb7..2a3084fd3eb 100644 --- a/homeassistant/components/yeelight/translations/cs.json +++ b/homeassistant/components/yeelight/translations/cs.json @@ -27,7 +27,7 @@ "init": { "data": { "model": "Model (voliteln\u00fd)", - "nightlight_switch": "Pou\u017e\u00edt p\u0159ep\u00edna\u010d no\u010dn\u00edho osv\u011btlen\u00ed", + "nightlight_switch": "Pou\u017e\u00edt vyp\u00edna\u010d no\u010dn\u00edho osv\u011btlen\u00ed", "save_on_change": "Ulo\u017eit stav p\u0159i zm\u011bn\u011b", "transition": "\u010cas p\u0159echodu (v ms)", "use_music_mode": "Povolit hudebn\u00ed re\u017eim" diff --git a/homeassistant/components/yeelight/translations/fr.json b/homeassistant/components/yeelight/translations/fr.json index b2153e6aec0..a319f15e36a 100644 --- a/homeassistant/components/yeelight/translations/fr.json +++ b/homeassistant/components/yeelight/translations/fr.json @@ -32,7 +32,7 @@ "model": "Mod\u00e8le", "nightlight_switch": "Utiliser le commutateur de veilleuse", "save_on_change": "Enregistrer l'\u00e9tat lors d'un changement", - "transition": "Dur\u00e9e de transition (en millisecondes)", + "transition": "Dur\u00e9e de la transition (en millisecondes)", "use_music_mode": "Activer le mode musique" }, "description": "Si vous ne pr\u00e9cisez pas le mod\u00e8le, il sera automatiquement d\u00e9tect\u00e9." diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index a6820321243..1b76aef1328 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -10,8 +10,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.const import CONF_HOST @@ -23,8 +22,6 @@ import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -SUPPORT_YEELIGHT_SUNFLOWER = SUPPORT_BRIGHTNESS | SUPPORT_COLOR - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_HOST): cv.string}) @@ -48,6 +45,9 @@ def setup_platform( class SunflowerBulb(LightEntity): """Representation of a Yeelight Sunflower Light.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, light): """Initialize a Yeelight Sunflower bulb.""" self._light = light @@ -87,11 +87,6 @@ class SunflowerBulb(LightEntity): """Return the color property.""" return color_util.color_RGB_to_hs(*self._rgb_color) - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_YEELIGHT_SUNFLOWER - def turn_on(self, **kwargs): """Instruct the light to turn on, optionally set colour/brightness.""" # when no arguments, just turn light on (full brightness) diff --git a/homeassistant/components/youless/translations/hu.json b/homeassistant/components/youless/translations/hu.json index 31913b7fa6f..415753be254 100644 --- a/homeassistant/components/youless/translations/hu.json +++ b/homeassistant/components/youless/translations/hu.json @@ -7,7 +7,7 @@ "user": { "data": { "host": "C\u00edm", - "name": "N\u00e9v" + "name": "Elnevez\u00e9s" } } } diff --git a/homeassistant/components/zengge/light.py b/homeassistant/components/zengge/light.py index 0a4392ff855..057b049eefd 100644 --- a/homeassistant/components/zengge/light.py +++ b/homeassistant/components/zengge/light.py @@ -9,11 +9,9 @@ from zengge import zengge from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - ATTR_WHITE_VALUE, + ATTR_WHITE, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_WHITE_VALUE, + ColorMode, LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_NAME @@ -25,8 +23,6 @@ import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -SUPPORT_ZENGGE_LED = SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE - DEVICE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME): cv.string}) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -103,20 +99,27 @@ class ZenggeLight(LightEntity): return self._white @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_ZENGGE_LED + def color_mode(self) -> ColorMode: + """Return the current color mode.""" + if self._white != 0: + return ColorMode.WHITE + return ColorMode.HS + + @property + def supported_color_modes(self) -> set[ColorMode | str]: + """Flag supported color modes.""" + return {ColorMode.HS, ColorMode.WHITE} @property def assumed_state(self): """We can report the actual state.""" return False - def set_rgb(self, red, green, blue): + def _set_rgb(self, red, green, blue): """Set the rgb state.""" return self._bulb.set_rgb(red, green, blue) - def set_white(self, white): + def _set_white(self, white): """Set the white state.""" return self._bulb.set_white(white) @@ -126,28 +129,29 @@ class ZenggeLight(LightEntity): self._bulb.on() hs_color = kwargs.get(ATTR_HS_COLOR) - white = kwargs.get(ATTR_WHITE_VALUE) + white = kwargs.get(ATTR_WHITE) brightness = kwargs.get(ATTR_BRIGHTNESS) if white is not None: - self._white = white + # Change the bulb to white + self._brightness = self._white = white self._hs_color = (0, 0) if hs_color is not None: + # Change the bulb to hs self._white = 0 self._hs_color = hs_color if brightness is not None: - self._white = 0 self._brightness = brightness if self._white != 0: - self.set_white(self._white) + self._set_white(self._brightness) else: rgb = color_util.color_hsv_to_RGB( self._hs_color[0], self._hs_color[1], self._brightness / 255 * 100 ) - self.set_rgb(*rgb) + self._set_rgb(*rgb) def turn_off(self, **kwargs): """Turn the specified light off.""" @@ -161,4 +165,6 @@ class ZenggeLight(LightEntity): self._hs_color = hsv[:2] self._brightness = (hsv[2] / 100) * 255 self._white = self._bulb.get_white() + if self._white: + self._brightness = self._white self._state = self._bulb.get_on() diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index dc4c7c001ae..e1ea2c82b1f 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.38.4"], + "requirements": ["zeroconf==0.38.5"], "dependencies": ["network", "api"], "codeowners": ["@bdraco"], "quality_scale": "internal", diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index bc9b3cae410..9bc2e4d29f3 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -9,8 +9,7 @@ import pyzerproc from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -25,8 +24,6 @@ from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN _LOGGER = logging.getLogger(__name__) -SUPPORT_ZERPROC = SUPPORT_BRIGHTNESS | SUPPORT_COLOR - DISCOVERY_INTERVAL = timedelta(seconds=60) @@ -81,6 +78,9 @@ async def async_setup_entry( class ZerprocLight(LightEntity): """Representation of an Zerproc Light.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, light): """Initialize a Zerproc light.""" self._light = light @@ -131,11 +131,6 @@ class ZerprocLight(LightEntity): """Return the icon to use in the frontend.""" return "mdi:string-lights" - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_ZERPROC - @property def brightness(self): """Return the brightness of the light.""" diff --git a/homeassistant/components/zha/alarm_control_panel.py b/homeassistant/components/zha/alarm_control_panel.py index 17dc47ebefa..ef616a8f894 100644 --- a/homeassistant/components/zha/alarm_control_panel.py +++ b/homeassistant/components/zha/alarm_control_panel.py @@ -4,12 +4,9 @@ import functools from zigpy.zcl.clusters.security import IasAce from homeassistant.components.alarm_control_panel import ( - FORMAT_TEXT, - SUPPORT_ALARM_ARM_AWAY, - SUPPORT_ALARM_ARM_HOME, - SUPPORT_ALARM_ARM_NIGHT, - SUPPORT_ALARM_TRIGGER, AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, + CodeFormat, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -79,6 +76,13 @@ async def async_setup_entry( class ZHAAlarmControlPanel(ZhaEntity, AlarmControlPanelEntity): """Entity for ZHA alarm control devices.""" + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.TRIGGER + ) + def __init__(self, unique_id, zha_device: ZhaDeviceType, channels, **kwargs): """Initialize the ZHA alarm control device.""" super().__init__(unique_id, zha_device, channels, **kwargs) @@ -112,7 +116,7 @@ class ZHAAlarmControlPanel(ZhaEntity, AlarmControlPanelEntity): @property def code_format(self): """Regex for code format or None if no code is required.""" - return FORMAT_TEXT + return CodeFormat.TEXT @property def changed_by(self): @@ -148,16 +152,6 @@ class ZHAAlarmControlPanel(ZhaEntity, AlarmControlPanelEntity): """Send alarm trigger command.""" self.async_write_ha_state() - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return ( - SUPPORT_ALARM_ARM_HOME - | SUPPORT_ALARM_ARM_AWAY - | SUPPORT_ALARM_ARM_NIGHT - | SUPPORT_ALARM_TRIGGER - ) - @property def state(self): """Return the state of the entity.""" diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index abfa94f5906..f130936df02 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -41,7 +41,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index 06b1e8a47d8..291e8413e16 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -17,28 +17,16 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, FAN_AUTO, FAN_ON, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -82,40 +70,40 @@ ATTR_UNOCCP_COOL_SETPT = "unoccupied_cooling_setpoint" STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, Platform.CLIMATE) MULTI_MATCH = functools.partial(ZHA_ENTITIES.multipass_match, Platform.CLIMATE) -RUNNING_MODE = {0x00: HVAC_MODE_OFF, 0x03: HVAC_MODE_COOL, 0x04: HVAC_MODE_HEAT} +RUNNING_MODE = {0x00: HVACMode.OFF, 0x03: HVACMode.COOL, 0x04: HVACMode.HEAT} SEQ_OF_OPERATION = { - 0x00: (HVAC_MODE_OFF, HVAC_MODE_COOL), # cooling only - 0x01: (HVAC_MODE_OFF, HVAC_MODE_COOL), # cooling with reheat - 0x02: (HVAC_MODE_OFF, HVAC_MODE_HEAT), # heating only - 0x03: (HVAC_MODE_OFF, HVAC_MODE_HEAT), # heating with reheat + 0x00: (HVACMode.OFF, HVACMode.COOL), # cooling only + 0x01: (HVACMode.OFF, HVACMode.COOL), # cooling with reheat + 0x02: (HVACMode.OFF, HVACMode.HEAT), # heating only + 0x03: (HVACMode.OFF, HVACMode.HEAT), # heating with reheat # cooling and heating 4-pipes - 0x04: (HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT), + 0x04: (HVACMode.OFF, HVACMode.HEAT_COOL, HVACMode.COOL, HVACMode.HEAT), # cooling and heating 4-pipes - 0x05: (HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT), - 0x06: (HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF), # centralite specific - 0x07: (HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF), # centralite specific + 0x05: (HVACMode.OFF, HVACMode.HEAT_COOL, HVACMode.COOL, HVACMode.HEAT), + 0x06: (HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF), # centralite specific + 0x07: (HVACMode.HEAT_COOL, HVACMode.OFF), # centralite specific } HVAC_MODE_2_SYSTEM = { - HVAC_MODE_OFF: T.SystemMode.Off, - HVAC_MODE_HEAT_COOL: T.SystemMode.Auto, - HVAC_MODE_COOL: T.SystemMode.Cool, - HVAC_MODE_HEAT: T.SystemMode.Heat, - HVAC_MODE_FAN_ONLY: T.SystemMode.Fan_only, - HVAC_MODE_DRY: T.SystemMode.Dry, + HVACMode.OFF: T.SystemMode.Off, + HVACMode.HEAT_COOL: T.SystemMode.Auto, + HVACMode.COOL: T.SystemMode.Cool, + HVACMode.HEAT: T.SystemMode.Heat, + HVACMode.FAN_ONLY: T.SystemMode.Fan_only, + HVACMode.DRY: T.SystemMode.Dry, } SYSTEM_MODE_2_HVAC = { - T.SystemMode.Off: HVAC_MODE_OFF, - T.SystemMode.Auto: HVAC_MODE_HEAT_COOL, - T.SystemMode.Cool: HVAC_MODE_COOL, - T.SystemMode.Heat: HVAC_MODE_HEAT, - T.SystemMode.Emergency_Heating: HVAC_MODE_HEAT, - T.SystemMode.Pre_cooling: HVAC_MODE_COOL, # this is 'precooling'. is it the same? - T.SystemMode.Fan_only: HVAC_MODE_FAN_ONLY, - T.SystemMode.Dry: HVAC_MODE_DRY, - T.SystemMode.Sleep: HVAC_MODE_OFF, + T.SystemMode.Off: HVACMode.OFF, + T.SystemMode.Auto: HVACMode.HEAT_COOL, + T.SystemMode.Cool: HVACMode.COOL, + T.SystemMode.Heat: HVACMode.HEAT, + T.SystemMode.Emergency_Heating: HVACMode.HEAT, + T.SystemMode.Pre_cooling: HVACMode.COOL, # this is 'precooling'. is it the same? + T.SystemMode.Fan_only: HVACMode.FAN_ONLY, + T.SystemMode.Dry: HVACMode.DRY, + T.SystemMode.Sleep: HVACMode.OFF, } ZCL_TEMP = 100 @@ -155,7 +143,7 @@ class Thermostat(ZhaEntity, ClimateEntity): self._thrm = self.cluster_channels.get(CHANNEL_THERMOSTAT) self._preset = PRESET_NONE self._presets = [] - self._supported_flags = SUPPORT_TARGET_TEMPERATURE + self._supported_flags = ClimateEntityFeature.TARGET_TEMPERATURE self._fan = self.cluster_channels.get(CHANNEL_FAN) @property @@ -214,7 +202,7 @@ class Thermostat(ZhaEntity, ClimateEntity): return [FAN_AUTO, FAN_ON] @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current HVAC action.""" if ( self._thrm.pi_heating_demand is None @@ -224,7 +212,7 @@ class Thermostat(ZhaEntity, ClimateEntity): return self._pi_demand_action @property - def _rm_rs_action(self) -> str | None: + def _rm_rs_action(self) -> HVACAction | None: """Return the current HVAC action based on running mode and running state.""" if (running_state := self._thrm.running_state) is None: @@ -232,47 +220,47 @@ class Thermostat(ZhaEntity, ClimateEntity): if running_state & ( T.RunningState.Heat_State_On | T.RunningState.Heat_2nd_Stage_On ): - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if running_state & ( T.RunningState.Cool_State_On | T.RunningState.Cool_2nd_Stage_On ): - return CURRENT_HVAC_COOL + return HVACAction.COOLING if running_state & ( T.RunningState.Fan_State_On | T.RunningState.Fan_2nd_Stage_On | T.RunningState.Fan_3rd_Stage_On ): - return CURRENT_HVAC_FAN + return HVACAction.FAN if running_state & T.RunningState.Idle: - return CURRENT_HVAC_IDLE - if self.hvac_mode != HVAC_MODE_OFF: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + return HVACAction.IDLE + if self.hvac_mode != HVACMode.OFF: + return HVACAction.IDLE + return HVACAction.OFF @property - def _pi_demand_action(self) -> str | None: + def _pi_demand_action(self) -> HVACAction | None: """Return the current HVAC action based on pi_demands.""" heating_demand = self._thrm.pi_heating_demand if heating_demand is not None and heating_demand > 0: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING cooling_demand = self._thrm.pi_cooling_demand if cooling_demand is not None and cooling_demand > 0: - return CURRENT_HVAC_COOL + return HVACAction.COOLING - if self.hvac_mode != HVAC_MODE_OFF: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + if self.hvac_mode != HVACMode.OFF: + return HVACAction.IDLE + return HVACAction.OFF @property - def hvac_mode(self) -> str | None: + def hvac_mode(self) -> HVACMode | None: """Return HVAC operation mode.""" return SYSTEM_MODE_2_HVAC.get(self._thrm.system_mode) @property - def hvac_modes(self) -> tuple[str, ...]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available HVAC operation modes.""" - return SEQ_OF_OPERATION.get(self._thrm.ctrl_sequence_of_oper, (HVAC_MODE_OFF,)) + return SEQ_OF_OPERATION.get(self._thrm.ctrl_sequence_of_oper, [HVACMode.OFF]) @property def precision(self): @@ -293,22 +281,22 @@ class Thermostat(ZhaEntity, ClimateEntity): def supported_features(self): """Return the list of supported features.""" features = self._supported_flags - if HVAC_MODE_HEAT_COOL in self.hvac_modes: - features |= SUPPORT_TARGET_TEMPERATURE_RANGE + if HVACMode.HEAT_COOL in self.hvac_modes: + features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE if self._fan is not None: - self._supported_flags |= SUPPORT_FAN_MODE + self._supported_flags |= ClimateEntityFeature.FAN_MODE return features @property def target_temperature(self): """Return the temperature we try to reach.""" temp = None - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: if self.preset_mode == PRESET_AWAY: temp = self._thrm.unoccupied_cooling_setpoint else: temp = self._thrm.occupied_cooling_setpoint - elif self.hvac_mode == HVAC_MODE_HEAT: + elif self.hvac_mode == HVACMode.HEAT: if self.preset_mode == PRESET_AWAY: temp = self._thrm.unoccupied_heating_setpoint else: @@ -320,7 +308,7 @@ class Thermostat(ZhaEntity, ClimateEntity): @property def target_temperature_high(self): """Return the upper bound temperature we try to reach.""" - if self.hvac_mode != HVAC_MODE_HEAT_COOL: + if self.hvac_mode != HVACMode.HEAT_COOL: return None if self.preset_mode == PRESET_AWAY: temp = self._thrm.unoccupied_cooling_setpoint @@ -335,7 +323,7 @@ class Thermostat(ZhaEntity, ClimateEntity): @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" - if self.hvac_mode != HVAC_MODE_HEAT_COOL: + if self.hvac_mode != HVACMode.HEAT_COOL: return None if self.preset_mode == PRESET_AWAY: temp = self._thrm.unoccupied_heating_setpoint @@ -355,9 +343,9 @@ class Thermostat(ZhaEntity, ClimateEntity): def max_temp(self) -> float: """Return the maximum temperature.""" temps = [] - if HVAC_MODE_HEAT in self.hvac_modes: + if HVACMode.HEAT in self.hvac_modes: temps.append(self._thrm.max_heat_setpoint_limit) - if HVAC_MODE_COOL in self.hvac_modes: + if HVACMode.COOL in self.hvac_modes: temps.append(self._thrm.max_cool_setpoint_limit) if not temps: @@ -368,9 +356,9 @@ class Thermostat(ZhaEntity, ClimateEntity): def min_temp(self) -> float: """Return the minimum temperature.""" temps = [] - if HVAC_MODE_HEAT in self.hvac_modes: + if HVACMode.HEAT in self.hvac_modes: temps.append(self._thrm.min_heat_setpoint_limit) - if HVAC_MODE_COOL in self.hvac_modes: + if HVACMode.COOL in self.hvac_modes: temps.append(self._thrm.min_cool_setpoint_limit) if not temps: @@ -412,7 +400,7 @@ class Thermostat(ZhaEntity, ClimateEntity): await self._fan.async_set_speed(mode) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" if hvac_mode not in self.hvac_modes: self.warning( @@ -457,7 +445,7 @@ class Thermostat(ZhaEntity, ClimateEntity): await self.async_set_hvac_mode(hvac_mode) thrm = self._thrm - if self.hvac_mode == HVAC_MODE_HEAT_COOL: + if self.hvac_mode == HVACMode.HEAT_COOL: success = True if low_temp is not None: low_temp = int(low_temp * ZCL_TEMP) @@ -473,11 +461,11 @@ class Thermostat(ZhaEntity, ClimateEntity): self.debug("Setting cooling %s setpoint: %s", low_temp, success) elif temp is not None: temp = int(temp * ZCL_TEMP) - if self.hvac_mode == HVAC_MODE_COOL: + if self.hvac_mode == HVACMode.COOL: success = await thrm.async_set_cooling_setpoint( temp, self.preset_mode == PRESET_AWAY ) - elif self.hvac_mode == HVAC_MODE_HEAT: + elif self.hvac_mode == HVACMode.HEAT: success = await thrm.async_set_heating_setpoint( temp, self.preset_mode == PRESET_AWAY ) @@ -513,7 +501,7 @@ class SinopeTechnologiesThermostat(Thermostat): """Initialize ZHA Thermostat instance.""" super().__init__(unique_id, zha_device, channels, **kwargs) self._presets = [PRESET_AWAY, PRESET_NONE] - self._supported_flags |= SUPPORT_PRESET_MODE + self._supported_flags |= ClimateEntityFeature.PRESET_MODE self._manufacturer_ch = self.cluster_channels["sinope_manufacturer_specific"] @property @@ -522,9 +510,9 @@ class SinopeTechnologiesThermostat(Thermostat): running_mode = self._thrm.running_mode if running_mode == T.SystemMode.Heat: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if running_mode == T.SystemMode.Cool: - return CURRENT_HVAC_COOL + return HVACAction.COOLING running_state = self._thrm.running_state if running_state and running_state & ( @@ -532,10 +520,10 @@ class SinopeTechnologiesThermostat(Thermostat): | T.RunningState.Fan_2nd_Stage_On | T.RunningState.Fan_3rd_Stage_On ): - return CURRENT_HVAC_FAN - if self.hvac_mode != HVAC_MODE_OFF and running_mode == T.SystemMode.Off: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + return HVACAction.FAN + if self.hvac_mode != HVACMode.OFF and running_mode == T.SystemMode.Off: + return HVACAction.IDLE + return HVACAction.OFF @callback def _async_update_time(self, timestamp=None) -> None: @@ -574,7 +562,7 @@ class SinopeTechnologiesThermostat(Thermostat): @MULTI_MATCH( channel_names=CHANNEL_THERMOSTAT, aux_channels=CHANNEL_FAN, - manufacturers="Zen Within", + manufacturers={"Zen Within", "LUX"}, stop_on_match_group=CHANNEL_THERMOSTAT, ) class ZenWithinThermostat(Thermostat): @@ -624,12 +612,12 @@ class MoesThermostat(Thermostat): PRESET_BOOST, PRESET_COMPLEX, ] - self._supported_flags |= SUPPORT_PRESET_MODE + self._supported_flags |= ClimateEntityFeature.PRESET_MODE @property - def hvac_modes(self) -> tuple[str, ...]: + def hvac_modes(self) -> list[HVACMode]: """Return only the heat mode, because the device can't be turned off.""" - return (HVAC_MODE_HEAT,) + return [HVACMode.HEAT] async def async_attribute_updated(self, record): """Handle attribute update from device.""" @@ -705,12 +693,12 @@ class BecaThermostat(Thermostat): PRESET_BOOST, PRESET_TEMP_MANUAL, ] - self._supported_flags |= SUPPORT_PRESET_MODE + self._supported_flags |= ClimateEntityFeature.PRESET_MODE @property - def hvac_modes(self) -> tuple[str, ...]: + def hvac_modes(self) -> list[HVACMode]: """Return only the heat mode, because the device can't be turned off.""" - return (HVAC_MODE_HEAT,) + return [HVACMode.HEAT] async def async_attribute_updated(self, record): """Handle attribute update from device.""" @@ -770,9 +758,9 @@ class StelproFanHeater(Thermostat): """Stelpro Fan Heater implementation.""" @property - def hvac_modes(self) -> tuple[str, ...]: + def hvac_modes(self) -> list[HVACMode]: """Return only the heat mode, because the device can't be turned off.""" - return (HVAC_MODE_HEAT,) + return [HVACMode.HEAT] @STRICT_MATCH( @@ -804,7 +792,7 @@ class ZONNSMARTThermostat(Thermostat): PRESET_SCHEDULE, self.PRESET_FROST, ] - self._supported_flags |= SUPPORT_PRESET_MODE + self._supported_flags |= ClimateEntityFeature.PRESET_MODE async def async_attribute_updated(self, record): """Handle attribute update from device.""" diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 2011f92a63b..00409794473 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -from collections.abc import Coroutine from typing import TYPE_CHECKING, Any, TypeVar import zigpy.endpoint @@ -50,7 +49,6 @@ class Channels: self._pools: list[ChannelPool] = [] self._power_config: base.ZigbeeChannel | None = None self._identify: base.ZigbeeChannel | None = None - self._semaphore = asyncio.Semaphore(3) self._unique_id = str(zha_device.ieee) self._zdo_channel = base.ZDOChannel(zha_device.device.endpoints[0], zha_device) self._zha_device = zha_device @@ -82,11 +80,6 @@ class Channels: if self._identify is None: self._identify = channel - @property - def semaphore(self) -> asyncio.Semaphore: - """Return semaphore for concurrent tasks.""" - return self._semaphore - @property def zdo_channel(self) -> base.ZDOChannel: """Return ZDO channel.""" @@ -336,13 +329,8 @@ class ChannelPool: async def _execute_channel_tasks(self, func_name: str, *args: Any) -> None: """Add a throttled channel task and swallow exceptions.""" - - async def _throttle(coro: Coroutine[Any, Any, None]) -> None: - async with self._channels.semaphore: - return await coro - channels = [*self.claimed_channels.values(), *self.client_channels.values()] - tasks = [_throttle(getattr(ch, func_name)(*args)) for ch in channels] + tasks = [getattr(ch, func_name)(*args) for ch in channels] results = await asyncio.gather(*tasks, return_exceptions=True) for channel, outcome in zip(channels, results): if isinstance(outcome, Exception): diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index b10209f45f4..7beefe2f0d0 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -310,11 +310,14 @@ class ZigbeeChannel(LogMixin): """Set cluster binding and attribute reporting.""" if not self._ch_pool.skip_configuration: if self.BIND: + self.debug("Performing cluster binding") await self.bind() if self.cluster.is_server: + self.debug("Configuring cluster attribute reporting") await self.configure_reporting() ch_specific_cfg = getattr(self, "async_configure_channel_specific", None) if ch_specific_cfg: + self.debug("Performing channel specific configuration") await ch_specific_cfg() self.debug("finished channel configuration") else: @@ -325,6 +328,7 @@ class ZigbeeChannel(LogMixin): async def async_initialize(self, from_cache: bool) -> None: """Initialize channel.""" if not from_cache and self._ch_pool.skip_configuration: + self.debug("Skipping channel initialization") self._status = ChannelStatus.INITIALIZED return @@ -334,12 +338,23 @@ class ZigbeeChannel(LogMixin): uncached.extend([cfg["attr"] for cfg in self.REPORT_CONFIG]) if cached: - await self._get_attributes(True, cached, from_cache=True) + self.debug("initializing cached channel attributes: %s", cached) + await self._get_attributes( + True, cached, from_cache=True, only_cache=from_cache + ) if uncached: - await self._get_attributes(True, uncached, from_cache=from_cache) + self.debug( + "initializing uncached channel attributes: %s - from cache[%s]", + uncached, + from_cache, + ) + await self._get_attributes( + True, uncached, from_cache=from_cache, only_cache=from_cache + ) ch_specific_init = getattr(self, "async_initialize_channel_specific", None) if ch_specific_init: + self.debug("Performing channel specific initialization: %s", uncached) await ch_specific_init(from_cache=from_cache) self.debug("finished channel initialization") @@ -407,7 +422,7 @@ class ZigbeeChannel(LogMixin): self._cluster, [attribute], allow_cache=from_cache, - only_cache=from_cache and not self._ch_pool.is_mains_powered, + only_cache=from_cache, manufacturer=manufacturer, ) return result.get(attribute) @@ -417,6 +432,7 @@ class ZigbeeChannel(LogMixin): raise_exceptions: bool, attributes: list[int | str], from_cache: bool = True, + only_cache: bool = True, ) -> dict[int | str, Any]: """Get the values for a list of attributes.""" manufacturer = None @@ -428,17 +444,18 @@ class ZigbeeChannel(LogMixin): result = {} while chunk: try: + self.debug("Reading attributes in chunks: %s", chunk) read, _ = await self.cluster.read_attributes( attributes, allow_cache=from_cache, - only_cache=from_cache and not self._ch_pool.is_mains_powered, + only_cache=only_cache, manufacturer=manufacturer, ) result.update(read) except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException) as ex: self.debug( "failed to get attributes '%s' on '%s' cluster: %s", - attributes, + chunk, self.cluster.ep_attribute, str(ex), ) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index 09a1fd80f17..e6524c9aad1 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -6,6 +6,7 @@ from collections.abc import Coroutine from typing import Any import zigpy.exceptions +import zigpy.types as t from zigpy.zcl.clusters import general from zigpy.zcl.foundation import Status @@ -291,13 +292,15 @@ class OnOffChannel(ZigbeeChannel): ON_OFF = 0 REPORT_CONFIG = ({"attr": "on_off", "config": REPORT_CONFIG_IMMEDIATE},) + ZCL_INIT_ATTRS = { + "start_up_on_off": True, + } def __init__( self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType ) -> None: """Initialize OnOffChannel.""" super().__init__(cluster, ch_pool) - self._state = None self._off_listener = None @property @@ -311,9 +314,9 @@ class OnOffChannel(ZigbeeChannel): cmd = parse_and_log_command(self, tsn, command_id, args) if cmd in ("off", "off_with_effect"): - self.attribute_updated(self.ON_OFF, False) + self.cluster.update_attribute(self.ON_OFF, t.Bool.false) elif cmd in ("on", "on_with_recall_global_scene"): - self.attribute_updated(self.ON_OFF, True) + self.cluster.update_attribute(self.ON_OFF, t.Bool.true) elif cmd == "on_with_timed_off": should_accept = args[0] on_time = args[1] @@ -322,7 +325,7 @@ class OnOffChannel(ZigbeeChannel): if self._off_listener is not None: self._off_listener() self._off_listener = None - self.attribute_updated(self.ON_OFF, True) + self.cluster.update_attribute(self.ON_OFF, t.Bool.true) if on_time > 0: self._off_listener = async_call_later( self._ch_pool.hass, @@ -330,13 +333,13 @@ class OnOffChannel(ZigbeeChannel): self.set_to_off, ) elif cmd == "toggle": - self.attribute_updated(self.ON_OFF, not bool(self._state)) + self.cluster.update_attribute(self.ON_OFF, not bool(self.on_off)) @callback def set_to_off(self, *_): """Set the state to off.""" self._off_listener = None - self.attribute_updated(self.ON_OFF, False) + self.cluster.update_attribute(self.ON_OFF, t.Bool.false) @callback def attribute_updated(self, attrid, value): @@ -345,11 +348,6 @@ class OnOffChannel(ZigbeeChannel): self.async_send_signal( f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}", attrid, "on_off", value ) - self._state = bool(value) - - async def async_initialize_channel_specific(self, from_cache: bool) -> None: - """Initialize channel.""" - self._state = self.on_off async def async_update(self): """Initialize channel.""" @@ -357,9 +355,7 @@ class OnOffChannel(ZigbeeChannel): return from_cache = not self._ch_pool.is_mains_powered self.debug("attempting to update onoff state - from cache: %s", from_cache) - state = await self.get_attribute_value(self.ON_OFF, from_cache=from_cache) - if state is not None: - self._state = bool(state) + await self.get_attribute_value(self.ON_OFF, from_cache=from_cache) await super().async_update() @@ -460,7 +456,9 @@ class PowerConfigurationChannel(ZigbeeChannel): "battery_size", "battery_quantity", ] - return self.get_attributes(attributes, from_cache=from_cache) + return self.get_attributes( + attributes, from_cache=from_cache, only_cache=from_cache + ) @registries.ZIGBEE_CHANNEL_REGISTRY.register(general.PowerProfile.cluster_id) diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index 5c3ef3a0f6c..e1019ed31bf 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -70,6 +70,8 @@ class ElectricalMeasurementChannel(ZigbeeChannel): {"attr": "rms_current_max", "config": REPORT_CONFIG_DEFAULT}, {"attr": "rms_voltage", "config": REPORT_CONFIG_OP}, {"attr": "rms_voltage_max", "config": REPORT_CONFIG_DEFAULT}, + {"attr": "ac_frequency", "config": REPORT_CONFIG_OP}, + {"attr": "ac_frequency_max", "config": REPORT_CONFIG_DEFAULT}, ) ZCL_INIT_ATTRS = { "ac_current_divisor": True, @@ -78,6 +80,8 @@ class ElectricalMeasurementChannel(ZigbeeChannel): "ac_power_multiplier": True, "ac_voltage_divisor": True, "ac_voltage_multiplier": True, + "ac_frequency_divisor": True, + "ac_frequency_multiplier": True, "measurement_type": True, "power_divisor": True, "power_multiplier": True, @@ -93,7 +97,7 @@ class ElectricalMeasurementChannel(ZigbeeChannel): for a in self.REPORT_CONFIG if a["attr"] not in self.cluster.unsupported_attributes ] - result = await self.get_attributes(attrs, from_cache=False) + result = await self.get_attributes(attrs, from_cache=False, only_cache=False) if result: for attr, value in result.items(): self.async_send_signal( @@ -123,6 +127,16 @@ class ElectricalMeasurementChannel(ZigbeeChannel): """Return ac voltage multiplier.""" return self.cluster.get("ac_voltage_multiplier") or 1 + @property + def ac_frequency_divisor(self) -> int: + """Return ac frequency divisor.""" + return self.cluster.get("ac_frequency_divisor") or 1 + + @property + def ac_frequency_multiplier(self) -> int: + """Return ac frequency multiplier.""" + return self.cluster.get("ac_frequency_multiplier") or 1 + @property def ac_power_divisor(self) -> int: """Return active power divisor.""" diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py index 19be861178f..0e1e0f8e8a3 100644 --- a/homeassistant/components/zha/core/channels/security.py +++ b/homeassistant/components/zha/core/channels/security.py @@ -84,7 +84,7 @@ class IasAce(ZigbeeChannel): @callback def cluster_command(self, tsn, command_id, args) -> None: """Handle commands received to this cluster.""" - self.warning( + self.debug( "received command %s", self._cluster.server_commands[command_id].name ) self.command_map[command_id](*args) @@ -120,7 +120,7 @@ class IasAce(ZigbeeChannel): code != self.panel_code and self.armed_state != AceCluster.PanelStatus.Panel_Disarmed ): - self.warning("Invalid code supplied to IAS ACE") + self.debug("Invalid code supplied to IAS ACE") self.invalid_tries += 1 zigbee_reply = self.arm_response( AceCluster.ArmNotification.Invalid_Arm_Disarm_Code @@ -131,12 +131,12 @@ class IasAce(ZigbeeChannel): self.armed_state == AceCluster.PanelStatus.Panel_Disarmed and self.alarm_status == AceCluster.AlarmStatus.No_Alarm ): - self.warning("IAS ACE already disarmed") + self.debug("IAS ACE already disarmed") zigbee_reply = self.arm_response( AceCluster.ArmNotification.Already_Disarmed ) else: - self.warning("Disarming all IAS ACE zones") + self.debug("Disarming all IAS ACE zones") zigbee_reply = self.arm_response( AceCluster.ArmNotification.All_Zones_Disarmed ) @@ -177,12 +177,12 @@ class IasAce(ZigbeeChannel): ) -> None: """Arm the panel with the specified statuses.""" if self.code_required_arm_actions and code != self.panel_code: - self.warning("Invalid code supplied to IAS ACE") + self.debug("Invalid code supplied to IAS ACE") zigbee_reply = self.arm_response( AceCluster.ArmNotification.Invalid_Arm_Disarm_Code ) else: - self.warning("Arming all IAS ACE zones") + self.debug("Arming all IAS ACE zones") self.armed_state = panel_status zigbee_reply = self.arm_response(armed_type) return zigbee_reply diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 41d90b48869..854d80ffb78 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -351,11 +351,15 @@ class ZHADevice(LogMixin): if self.is_coordinator: return if self.last_seen is None: + self.debug("last_seen is None, marking the device unavailable") self.update_available(False) return difference = time.time() - self.last_seen if difference < self.consider_unavailable_time: + self.debug( + "Device seen - marking the device available and resetting counter" + ) self.update_available(True) self._checkins_missed_count = 0 return @@ -365,6 +369,10 @@ class ZHADevice(LogMixin): or self.manufacturer == "LUMI" or not self._channels.pools ): + self.debug( + "last_seen is %s seconds ago and ping attempts have been exhausted, marking the device unavailable", + difference, + ) self.update_available(False) return @@ -386,13 +394,23 @@ class ZHADevice(LogMixin): def update_available(self, available: bool) -> None: """Update device availability and signal entities.""" + self.debug( + "Update device availability - device available: %s - new availability: %s - changed: %s", + self.available, + available, + self.available ^ available, + ) availability_changed = self.available ^ available self.available = available if availability_changed and available: # reinit channels then signal entities + self.debug( + "Device availability changed and device became available, reinitializing channels" + ) self.hass.async_create_task(self._async_became_available()) return if availability_changed and not available: + self.debug("Device availability changed and device became unavailable") self._channels.zha_send_event( { "device_event_type": "device_offline", diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 9f7523d41f0..cdad57834eb 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -54,14 +54,13 @@ async def async_add_entities( tuple[str, ZHADevice, list[base.ZigbeeChannel]], ] ], - update_before_add: bool = True, ) -> None: """Add entities helper.""" if not entities: return to_add = [ent_cls.create_entity(*args) for ent_cls, args in entities] entities_to_add = [entity for entity in to_add if entity is not None] - _async_add_entities(entities_to_add, update_before_add=update_before_add) + _async_add_entities(entities_to_add, update_before_add=False) entities.clear() @@ -78,6 +77,7 @@ class ProbeEndpoint: self.discover_by_device_type(channel_pool) self.discover_multi_entities(channel_pool) self.discover_by_cluster_id(channel_pool) + self.discover_multi_entities(channel_pool, config_diagnostic_entities=True) zha_regs.ZHA_ENTITIES.clean_up() @callback @@ -177,17 +177,28 @@ class ProbeEndpoint: @staticmethod @callback - def discover_multi_entities(channel_pool: ChannelPool) -> None: + def discover_multi_entities( + channel_pool: ChannelPool, + config_diagnostic_entities: bool = False, + ) -> None: """Process an endpoint on and discover multiple entities.""" ep_profile_id = channel_pool.endpoint.profile_id ep_device_type = channel_pool.endpoint.device_type cmpt_by_dev_type = zha_regs.DEVICE_CLASS[ep_profile_id].get(ep_device_type) - remaining_channels = channel_pool.unclaimed_channels() - matches, claimed = zha_regs.ZHA_ENTITIES.get_multi_entity( - channel_pool.manufacturer, channel_pool.model, remaining_channels - ) + if config_diagnostic_entities: + matches, claimed = zha_regs.ZHA_ENTITIES.get_config_diagnostic_entity( + channel_pool.manufacturer, + channel_pool.model, + list(channel_pool.all_channels.values()), + ) + else: + matches, claimed = zha_regs.ZHA_ENTITIES.get_multi_entity( + channel_pool.manufacturer, + channel_pool.model, + channel_pool.unclaimed_channels(), + ) channel_pool.claim_channels(claimed) for component, ent_n_chan_list in matches.items(): diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 64f7b24ff99..0ba375362f7 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -239,29 +239,25 @@ class ZHAGateway: async def async_initialize_devices_and_entities(self) -> None: """Initialize devices and load entities.""" - semaphore = asyncio.Semaphore(2) - async def _throttle(zha_device: ZHADevice, cached: bool) -> None: - async with semaphore: - await zha_device.async_initialize(from_cache=cached) - - _LOGGER.debug("Loading battery powered devices") + _LOGGER.debug("Loading all devices") await asyncio.gather( - *( - _throttle(dev, cached=True) - for dev in self.devices.values() - if not dev.is_mains_powered - ) + *(dev.async_initialize(from_cache=True) for dev in self.devices.values()) ) - _LOGGER.debug("Loading mains powered devices") - await asyncio.gather( - *( - _throttle(dev, cached=False) - for dev in self.devices.values() - if dev.is_mains_powered + async def fetch_updated_state() -> None: + """Fetch updated state for mains powered devices.""" + _LOGGER.debug("Fetching current state for mains powered devices") + await asyncio.gather( + *( + dev.async_initialize(from_cache=False) + for dev in self.devices.values() + if dev.is_mains_powered + ) ) - ) + + # background the fetching of state for mains powered devices + asyncio.create_task(fetch_updated_state()) def device_joined(self, device: zigpy.device.Device) -> None: """Handle device joined. diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 1d3482cd8f4..fb00e23ac6f 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -232,6 +232,11 @@ class ZHAEntityRegistry: ] = collections.defaultdict( lambda: collections.defaultdict(lambda: collections.defaultdict(list)) ) + self._config_diagnostic_entity_registry: dict[ + str, dict[int | str | None, dict[MatchRule, list[CALLABLE_T]]] + ] = collections.defaultdict( + lambda: collections.defaultdict(lambda: collections.defaultdict(list)) + ) self._group_registry: dict[str, CALLABLE_T] = {} self.single_device_matches: dict[ Platform, dict[EUI64, list[str]] @@ -278,6 +283,33 @@ class ZHAEntityRegistry: return result, list(all_claimed) + def get_config_diagnostic_entity( + self, + manufacturer: str, + model: str, + channels: list[ChannelType], + ) -> tuple[dict[str, list[EntityClassAndChannels]], list[ChannelType]]: + """Match ZHA Channels to potentially multiple ZHA Entity classes.""" + result: dict[str, list[EntityClassAndChannels]] = collections.defaultdict(list) + all_claimed: set[ChannelType] = set() + for ( + component, + stop_match_groups, + ) in self._config_diagnostic_entity_registry.items(): + for stop_match_grp, matches in stop_match_groups.items(): + sorted_matches = sorted(matches, key=lambda x: x.weight, reverse=True) + for match in sorted_matches: + if match.strict_matched(manufacturer, model, channels): + claimed = match.claim_channels(channels) + for ent_class in stop_match_groups[stop_match_grp][match]: + ent_n_channels = EntityClassAndChannels(ent_class, claimed) + result[component].append(ent_n_channels) + all_claimed |= set(claimed) + if stop_match_grp: + break + + return result, list(all_claimed) + def get_group_entity(self, component: str) -> CALLABLE_T: """Match a ZHA group to a ZHA Entity class.""" return self._group_registry.get(component) @@ -340,6 +372,39 @@ class ZHAEntityRegistry: return decorator + def config_diagnostic_match( + self, + component: str, + channel_names: set[str] | str = None, + generic_ids: set[str] | str = None, + manufacturers: Callable | set[str] | str = None, + models: Callable | set[str] | str = None, + aux_channels: Callable | set[str] | str = None, + stop_on_match_group: int | str | None = None, + ) -> Callable[[CALLABLE_T], CALLABLE_T]: + """Decorate a loose match rule.""" + + rule = MatchRule( + channel_names, + generic_ids, + manufacturers, + models, + aux_channels, + ) + + def decorator(zha_entity: CALLABLE_T) -> CALLABLE_T: + """Register a loose match rule. + + All non empty fields of a match rule must match. + """ + # group the rules by channels + self._config_diagnostic_entity_registry[component][stop_on_match_group][ + rule + ].append(zha_entity) + return zha_entity + + return decorator + def group_match(self, component: str) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a group match rule.""" diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 13e43aa9ff0..e9ea9ee871a 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -206,10 +206,8 @@ class ZhaEntity(BaseZhaEntity, RestoreEntity): signal_override=True, ) - if not self.zha_device.is_mains_powered: - # mains powered devices will get real time state - if last_state := await self.async_get_last_state(): - self.async_restore_last_state(last_state) + if last_state := await self.async_get_last_state(): + self.async_restore_last_state(last_state) self.async_accept_signal( None, @@ -291,6 +289,7 @@ class ZhaGroupEntity(BaseZhaEntity): async def async_added_to_hass(self) -> None: """Register callbacks.""" await super().async_added_to_hass() + await self.async_update() self.async_accept_signal( None, diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index e6f28935d10..1c2f52c6038 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -11,8 +11,8 @@ from zigpy.zcl.clusters import hvac from homeassistant.components.fan import ( ATTR_PERCENTAGE, ATTR_PRESET_MODE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, NotValidPresetModeError, ) from homeassistant.config_entries import ConfigEntry @@ -67,7 +67,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) @@ -76,16 +75,13 @@ async def async_setup_entry( class BaseFan(FanEntity): """Base representation of a ZHA fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + @property def preset_modes(self) -> list[str]: """Return the available preset modes.""" return PRESET_MODES - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED - @property def speed_count(self) -> int: """Return the number of speeds the fan supports.""" diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index b6d344a57e7..ef7205feb87 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -488,7 +488,7 @@ class Light(BaseLight, ZhaEntity): ] results = await self._color_channel.get_attributes( - attributes, from_cache=False + attributes, from_cache=False, only_cache=False ) if (color_mode := results.get("color_mode")) is not None: diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 3704e9715fb..8befa039382 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,9 +7,9 @@ "bellows==0.29.0", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.72", - "zigpy-deconz==0.14.0", - "zigpy==0.44.2", + "zha-quirks==0.0.73", + "zigpy-deconz==0.16.0", + "zigpy==0.45.1", "zigpy-xbee==0.14.0", "zigpy-zigate==0.7.4", "zigpy-znp==0.7.0" diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index 3c5a374e588..e1191b4ece4 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -250,7 +250,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index 7cb214566d1..afa2ee18da9 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -3,7 +3,9 @@ from __future__ import annotations from enum import Enum import functools +import logging +from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasWd from homeassistant.components.select import SelectEntity @@ -15,12 +17,21 @@ from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery -from .core.const import CHANNEL_IAS_WD, DATA_ZHA, SIGNAL_ADD_ENTITIES, Strobe +from .core.const import ( + CHANNEL_IAS_WD, + CHANNEL_ON_OFF, + DATA_ZHA, + SIGNAL_ADD_ENTITIES, + Strobe, +) from .core.registries import ZHA_ENTITIES from .core.typing import ChannelType, ZhaDeviceType from .entity import ZhaEntity -MULTI_MATCH = functools.partial(ZHA_ENTITIES.multipass_match, Platform.SELECT) +CONFIG_DIAGNOSTIC_MATCH = functools.partial( + ZHA_ENTITIES.config_diagnostic_match, Platform.SELECT +) +_LOGGER = logging.getLogger(__name__) async def async_setup_entry( @@ -38,7 +49,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) @@ -76,12 +86,6 @@ class ZHAEnumSelectEntity(ZhaEntity, SelectEntity): self._channel.data_cache[self._attr_name] = self._enum[option.replace(" ", "_")] self.async_write_ha_state() - async def async_added_to_hass(self) -> None: - """Run when about to be added to hass.""" - await super().async_added_to_hass() - if last_state := await self.async_get_last_state(): - self.async_restore_last_state(last_state) - @callback def async_restore_last_state(self, last_state) -> None: """Restore previous state.""" @@ -100,7 +104,7 @@ class ZHANonZCLSelectEntity(ZHAEnumSelectEntity): return True -@MULTI_MATCH(channel_names=CHANNEL_IAS_WD) +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_IAS_WD) class ZHADefaultToneSelectEntity( ZHANonZCLSelectEntity, id_suffix=IasWd.Warning.WarningMode.__name__ ): @@ -109,7 +113,7 @@ class ZHADefaultToneSelectEntity( _enum: Enum = IasWd.Warning.WarningMode -@MULTI_MATCH(channel_names=CHANNEL_IAS_WD) +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_IAS_WD) class ZHADefaultSirenLevelSelectEntity( ZHANonZCLSelectEntity, id_suffix=IasWd.Warning.SirenLevel.__name__ ): @@ -118,7 +122,7 @@ class ZHADefaultSirenLevelSelectEntity( _enum: Enum = IasWd.Warning.SirenLevel -@MULTI_MATCH(channel_names=CHANNEL_IAS_WD) +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_IAS_WD) class ZHADefaultStrobeLevelSelectEntity( ZHANonZCLSelectEntity, id_suffix=IasWd.StrobeLevel.__name__ ): @@ -127,8 +131,80 @@ class ZHADefaultStrobeLevelSelectEntity( _enum: Enum = IasWd.StrobeLevel -@MULTI_MATCH(channel_names=CHANNEL_IAS_WD) +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_IAS_WD) class ZHADefaultStrobeSelectEntity(ZHANonZCLSelectEntity, id_suffix=Strobe.__name__): """Representation of a ZHA default siren strobe select entity.""" _enum: Enum = Strobe + + +class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): + """Representation of a ZHA ZCL enum select entity.""" + + _select_attr: str + _attr_entity_category = EntityCategory.CONFIG + _enum: Enum + + @classmethod + def create_entity( + cls, + unique_id: str, + zha_device: ZhaDeviceType, + channels: list[ChannelType], + **kwargs, + ) -> ZhaEntity | None: + """Entity Factory. + + Return entity if it is a supported configuration, otherwise return None + """ + channel = channels[0] + if ( + cls._select_attr in channel.cluster.unsupported_attributes + or channel.cluster.get(cls._select_attr) is None + ): + _LOGGER.debug( + "%s is not supported - skipping %s entity creation", + cls._select_attr, + cls.__name__, + ) + return None + + return cls(unique_id, zha_device, channels, **kwargs) + + def __init__( + self, + unique_id: str, + zha_device: ZhaDeviceType, + channels: list[ChannelType], + **kwargs, + ) -> None: + """Init this select entity.""" + self._attr_options = [entry.name.replace("_", " ") for entry in self._enum] + self._channel: ChannelType = channels[0] + super().__init__(unique_id, zha_device, channels, **kwargs) + + @property + def current_option(self) -> str | None: + """Return the selected entity option to represent the entity state.""" + option = self._channel.cluster.get(self._select_attr) + if option is None: + return None + option = self._enum(option) + return option.name.replace("_", " ") + + async def async_select_option(self, option: str | int) -> None: + """Change the selected option.""" + await self._channel.cluster.write_attributes( + {self._select_attr: self._enum[option.replace(" ", "_")]} + ) + self.async_write_ha_state() + + +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_ON_OFF) +class ZHAStartupOnOffSelectEntity( + ZCLEnumSelectEntity, id_suffix=OnOff.StartUpOnOff.__name__ +): + """Representation of a ZHA startup onoff select entity.""" + + _select_attr = "start_up_on_off" + _enum: Enum = OnOff.StartUpOnOff diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 302bbc2a054..249034ef068 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -5,13 +5,7 @@ import functools import numbers from typing import Any -from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, - CURRENT_HVAC_OFF, -) +from homeassistant.components.climate.const import HVACAction from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -25,6 +19,7 @@ from homeassistant.const import ( ELECTRIC_CURRENT_AMPERE, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, + FREQUENCY_HERTZ, LIGHT_LUX, PERCENTAGE, POWER_VOLT_AMPERE, @@ -108,7 +103,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) @@ -346,6 +340,35 @@ class ElectricalMeasurementRMSVoltage(ElectricalMeasurement, id_suffix="rms_volt return False +@MULTI_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT) +class ElectricalMeasurementFrequency(ElectricalMeasurement, id_suffix="ac_frequency"): + """Frequency measurement.""" + + SENSOR_ATTR = "ac_frequency" + _attr_device_class: SensorDeviceClass = SensorDeviceClass.FREQUENCY + _unit = FREQUENCY_HERTZ + _div_mul_prefix = "ac_frequency" + + @property + def should_poll(self) -> bool: + """Poll indirectly by ElectricalMeasurementSensor.""" + return False + + +@MULTI_MATCH(channel_names=CHANNEL_ELECTRICAL_MEASUREMENT) +class ElectricalMeasurementPowerFactor(ElectricalMeasurement, id_suffix="power_factor"): + """Frequency measurement.""" + + SENSOR_ATTR = "power_factor" + _attr_device_class: SensorDeviceClass = SensorDeviceClass.POWER_FACTOR + _unit = PERCENTAGE + + @property + def should_poll(self) -> bool: + """Poll indirectly by ElectricalMeasurementSensor.""" + return False + + @MULTI_MATCH( generic_ids=CHANNEL_ST_HUMIDITY_CLUSTER, stop_on_match_group=CHANNEL_HUMIDITY ) @@ -601,7 +624,7 @@ class ThermostatHVACAction(Sensor, id_suffix="hvac_action"): return self._pi_demand_action @property - def _rm_rs_action(self) -> str | None: + def _rm_rs_action(self) -> HVACAction | None: """Return the current HVAC action based on running mode and running state.""" if (running_state := self._channel.running_state) is None: @@ -612,14 +635,14 @@ class ThermostatHVACAction(Sensor, id_suffix="hvac_action"): | self._channel.RunningState.Heat_2nd_Stage_On ) if running_state & rs_heat: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING rs_cool = ( self._channel.RunningState.Cool_State_On | self._channel.RunningState.Cool_2nd_Stage_On ) if running_state & rs_cool: - return CURRENT_HVAC_COOL + return HVACAction.COOLING running_state = self._channel.running_state if running_state and running_state & ( @@ -627,30 +650,30 @@ class ThermostatHVACAction(Sensor, id_suffix="hvac_action"): | self._channel.RunningState.Fan_2nd_Stage_On | self._channel.RunningState.Fan_3rd_Stage_On ): - return CURRENT_HVAC_FAN + return HVACAction.FAN running_state = self._channel.running_state if running_state and running_state & self._channel.RunningState.Idle: - return CURRENT_HVAC_IDLE + return HVACAction.IDLE if self._channel.system_mode != self._channel.SystemMode.Off: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + return HVACAction.IDLE + return HVACAction.OFF @property - def _pi_demand_action(self) -> str | None: + def _pi_demand_action(self) -> HVACAction: """Return the current HVAC action based on pi_demands.""" heating_demand = self._channel.pi_heating_demand if heating_demand is not None and heating_demand > 0: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING cooling_demand = self._channel.pi_cooling_demand if cooling_demand is not None and cooling_demand > 0: - return CURRENT_HVAC_COOL + return HVACAction.COOLING if self._channel.system_mode != self._channel.SystemMode.Off: - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + return HVACAction.IDLE + return HVACAction.OFF @callback def async_set_state(self, *args, **kwargs) -> None: @@ -667,14 +690,14 @@ class SinopeHVACAction(ThermostatHVACAction): """Sinope Thermostat HVAC action sensor.""" @property - def _rm_rs_action(self) -> str | None: + def _rm_rs_action(self) -> HVACAction: """Return the current HVAC action based on running mode and running state.""" running_mode = self._channel.running_mode if running_mode == self._channel.RunningMode.Heat: - return CURRENT_HVAC_HEAT + return HVACAction.HEATING if running_mode == self._channel.RunningMode.Cool: - return CURRENT_HVAC_COOL + return HVACAction.COOLING running_state = self._channel.running_state if running_state and running_state & ( @@ -682,21 +705,21 @@ class SinopeHVACAction(ThermostatHVACAction): | self._channel.RunningState.Fan_2nd_Stage_On | self._channel.RunningState.Fan_3rd_Stage_On ): - return CURRENT_HVAC_FAN + return HVACAction.FAN if ( self._channel.system_mode != self._channel.SystemMode.Off and running_mode == self._channel.SystemMode.Off ): - return CURRENT_HVAC_IDLE - return CURRENT_HVAC_OFF + return HVACAction.IDLE + return HVACAction.OFF @MULTI_MATCH(channel_names=CHANNEL_BASIC) class RSSISensor(Sensor, id_suffix="rssi"): """RSSI sensor for a device.""" - _state_class: SensorStateClass = SensorStateClass.MEASUREMENT - _device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH + _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT + _attr_device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_entity_registry_enabled_default = False diff --git a/homeassistant/components/zha/siren.py b/homeassistant/components/zha/siren.py index 5ba83dbef12..38b58b8dc54 100644 --- a/homeassistant/components/zha/siren.py +++ b/homeassistant/components/zha/siren.py @@ -1,5 +1,4 @@ """Support for ZHA sirens.""" - from __future__ import annotations import functools @@ -9,17 +8,10 @@ from zigpy.zcl.clusters.security import IasWd as WD from homeassistant.components.siren import ( ATTR_DURATION, - SUPPORT_DURATION, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SirenEntity, + SirenEntityFeature, ) -from homeassistant.components.siren.const import ( - ATTR_TONE, - ATTR_VOLUME_LEVEL, - SUPPORT_TONES, - SUPPORT_VOLUME_SET, -) +from homeassistant.components.siren.const import ATTR_TONE, ATTR_VOLUME_LEVEL from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback @@ -68,7 +60,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) @@ -87,11 +78,11 @@ class ZHASiren(ZhaEntity, SirenEntity): ) -> None: """Init this siren.""" self._attr_supported_features = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_DURATION - | SUPPORT_VOLUME_SET - | SUPPORT_TONES + SirenEntityFeature.TURN_ON + | SirenEntityFeature.TURN_OFF + | SirenEntityFeature.DURATION + | SirenEntityFeature.VOLUME_SET + | SirenEntityFeature.TONES ) self._attr_available_tones: list[int | str] | dict[int, str] | None = { WARNING_DEVICE_MODE_BURGLAR: "Burglar", diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 87d2407c2dc..254e3691da1 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -46,21 +46,73 @@ async def async_setup_entry( config_entry.async_on_unload(unsub) -class BaseSwitch(SwitchEntity): - """Common base class for zha switches.""" +@STRICT_MATCH(channel_names=CHANNEL_ON_OFF) +class Switch(ZhaEntity, SwitchEntity): + """ZHA switch.""" - def __init__(self, *args, **kwargs): + def __init__(self, unique_id, zha_device, channels, **kwargs): """Initialize the ZHA switch.""" - self._on_off_channel = None - self._state = None - super().__init__(*args, **kwargs) + super().__init__(unique_id, zha_device, channels, **kwargs) + self._on_off_channel = self.cluster_channels.get(CHANNEL_ON_OFF) @property def is_on(self) -> bool: """Return if the switch is on based on the statemachine.""" - if self._state is None: + if self._on_off_channel.on_off is None: return False - return self._state + return self._on_off_channel.on_off + + async def async_turn_on(self, **kwargs) -> None: + """Turn the entity on.""" + result = await self._on_off_channel.on() + if isinstance(result, Exception) or result[1] is not Status.SUCCESS: + return + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the entity off.""" + result = await self._on_off_channel.off() + if isinstance(result, Exception) or result[1] is not Status.SUCCESS: + return + self.async_write_ha_state() + + @callback + def async_set_state(self, attr_id: int, attr_name: str, value: Any): + """Handle state update from channel.""" + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Run when about to be added to hass.""" + await super().async_added_to_hass() + self.async_accept_signal( + self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_state + ) + + async def async_update(self) -> None: + """Attempt to retrieve on off state from the switch.""" + await super().async_update() + if self._on_off_channel: + await self._on_off_channel.get_attribute_value("on_off", from_cache=False) + + +@GROUP_MATCH() +class SwitchGroup(ZhaGroupEntity, SwitchEntity): + """Representation of a switch group.""" + + def __init__( + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs + ) -> None: + """Initialize a switch group.""" + super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) + self._available: bool + self._state: bool + group = self.zha_device.gateway.get_group(self._group_id) + self._on_off_channel = group.endpoint[OnOff.cluster_id] + + @property + def is_on(self) -> bool: + """Return if the switch is on based on the statemachine.""" + return bool(self._state) async def async_turn_on(self, **kwargs) -> None: """Turn the entity on.""" @@ -78,56 +130,6 @@ class BaseSwitch(SwitchEntity): self._state = False self.async_write_ha_state() - -@STRICT_MATCH(channel_names=CHANNEL_ON_OFF) -class Switch(BaseSwitch, ZhaEntity): - """ZHA switch.""" - - def __init__(self, unique_id, zha_device, channels, **kwargs): - """Initialize the ZHA switch.""" - super().__init__(unique_id, zha_device, channels, **kwargs) - self._on_off_channel = self.cluster_channels.get(CHANNEL_ON_OFF) - - @callback - def async_set_state(self, attr_id: int, attr_name: str, value: Any): - """Handle state update from channel.""" - self._state = bool(value) - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """Run when about to be added to hass.""" - await super().async_added_to_hass() - self.async_accept_signal( - self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_state - ) - - @callback - def async_restore_last_state(self, last_state) -> None: - """Restore previous state.""" - self._state = last_state.state == STATE_ON - - async def async_update(self) -> None: - """Attempt to retrieve on off state from the switch.""" - await super().async_update() - if self._on_off_channel: - state = await self._on_off_channel.get_attribute_value("on_off") - if state is not None: - self._state = state - - -@GROUP_MATCH() -class SwitchGroup(BaseSwitch, ZhaGroupEntity): - """Representation of a switch group.""" - - def __init__( - self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs - ) -> None: - """Initialize a switch group.""" - super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) - self._available: bool = False - group = self.zha_device.gateway.get_group(self._group_id) - self._on_off_channel = group.endpoint[OnOff.cluster_id] - async def async_update(self) -> None: """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] diff --git a/homeassistant/components/zha/translations/cs.json b/homeassistant/components/zha/translations/cs.json index 6a7d4aa17ea..de16e3bd387 100644 --- a/homeassistant/components/zha/translations/cs.json +++ b/homeassistant/components/zha/translations/cs.json @@ -35,7 +35,7 @@ "button_6": "\u0160est\u00e9 tla\u010d\u00edtko", "close": "Zav\u0159\u00edt", "dim_down": "ztmavit", - "dim_up": "ro\u017ehnout", + "dim_up": "rozjasnit", "face_1": "aktivov\u00e1no tv\u00e1\u0159\u00ed 1", "face_2": "aktivov\u00e1no tv\u00e1\u0159\u00ed 2", "face_3": "aktivov\u00e1no tv\u00e1\u0159\u00ed 3", diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index 6c04775d233..633dc50bcb7 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -10,13 +10,8 @@ from zhong_hong_hvac.hvac import HVAC as ZhongHongHVAC from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_OFF, - SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE, + ClimateEntityFeature, + HVACMode, ) from homeassistant.const import ( ATTR_TEMPERATURE, @@ -54,14 +49,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -SUPPORT_HVAC = [ - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_OFF, -] - ZHONG_HONG_MODE_COOL = "cool" ZHONG_HONG_MODE_HEAT = "heat" ZHONG_HONG_MODE_DRY = "dry" @@ -69,10 +56,10 @@ ZHONG_HONG_MODE_FAN_ONLY = "fan_only" MODE_TO_STATE = { - ZHONG_HONG_MODE_COOL: HVAC_MODE_COOL, - ZHONG_HONG_MODE_HEAT: HVAC_MODE_HEAT, - ZHONG_HONG_MODE_DRY: HVAC_MODE_DRY, - ZHONG_HONG_MODE_FAN_ONLY: HVAC_MODE_FAN_ONLY, + ZHONG_HONG_MODE_COOL: HVACMode.COOL, + ZHONG_HONG_MODE_HEAT: HVACMode.HEAT, + ZHONG_HONG_MODE_DRY: HVACMode.DRY, + ZHONG_HONG_MODE_FAN_ONLY: HVACMode.FAN_ONLY, } @@ -130,6 +117,17 @@ def setup_platform( class ZhongHongClimate(ClimateEntity): """Representation of a ZhongHong controller support HVAC.""" + _attr_hvac_modes = [ + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.DRY, + HVACMode.FAN_ONLY, + HVACMode.OFF, + ] + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) + def __init__(self, hub, addr_out, addr_in): """Set up the ZhongHong climate devices.""" @@ -177,27 +175,17 @@ class ZhongHongClimate(ClimateEntity): """Return the unique ID of the HVAC.""" return f"zhong_hong_hvac_{self._device.addr_out}_{self._device.addr_in}" - @property - def supported_features(self): - """Return the list of supported features.""" - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - @property def temperature_unit(self): """Return the unit of measurement used by the platform.""" return TEMP_CELSIUS @property - def hvac_mode(self): + def hvac_mode(self) -> HVACMode: """Return current operation ie. heat, cool, idle.""" if self.is_on: return self._current_operation - return HVAC_MODE_OFF - - @property - def hvac_modes(self): - """Return the list of available operation modes.""" - return SUPPORT_HVAC + return HVACMode.OFF @property def current_temperature(self): @@ -255,9 +243,9 @@ class ZhongHongClimate(ClimateEntity): if (operation_mode := kwargs.get(ATTR_HVAC_MODE)) is not None: self.set_hvac_mode(operation_mode) - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: if self.is_on: self.turn_off() return diff --git a/homeassistant/components/ziggo_mediabox_xl/media_player.py b/homeassistant/components/ziggo_mediabox_xl/media_player.py index 59e6e56e2c5..42c86020d2e 100644 --- a/homeassistant/components/ziggo_mediabox_xl/media_player.py +++ b/homeassistant/components/ziggo_mediabox_xl/media_player.py @@ -7,15 +7,10 @@ import socket import voluptuous as vol from ziggo_mediabox_xl import ZiggoMediaboxXL -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity -from homeassistant.components.media_player.const import ( - SUPPORT_NEXT_TRACK, - SUPPORT_PAUSE, - SUPPORT_PLAY, - SUPPORT_PREVIOUS_TRACK, - SUPPORT_SELECT_SOURCE, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + MediaPlayerEntity, + MediaPlayerEntityFeature, ) from homeassistant.const import ( CONF_HOST, @@ -33,16 +28,6 @@ _LOGGER = logging.getLogger(__name__) DATA_KNOWN_DEVICES = "ziggo_mediabox_xl_known_devices" -SUPPORT_ZIGGO = ( - SUPPORT_TURN_ON - | SUPPORT_TURN_OFF - | SUPPORT_NEXT_TRACK - | SUPPORT_PAUSE - | SUPPORT_PREVIOUS_TRACK - | SUPPORT_SELECT_SOURCE - | SUPPORT_PLAY -) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME): cv.string} ) @@ -104,6 +89,16 @@ def setup_platform( class ZiggoMediaboxXLDevice(MediaPlayerEntity): """Representation of a Ziggo Mediabox XL Device.""" + _attr_supported_features = ( + MediaPlayerEntityFeature.TURN_ON + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.NEXT_TRACK + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.PREVIOUS_TRACK + | MediaPlayerEntityFeature.SELECT_SOURCE + | MediaPlayerEntityFeature.PLAY + ) + def __init__(self, mediabox, host, name, available): """Initialize the device.""" self._mediabox = mediabox @@ -158,11 +153,6 @@ class ZiggoMediaboxXLDevice(MediaPlayerEntity): for c in sorted(self._mediabox.channels().keys()) ] - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_ZIGGO - def turn_on(self): """Turn the media player on.""" self.send_keys(["POWER"]) diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index ebde3328c02..9dac47eaafb 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -13,6 +13,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_LATITUDE, ATTR_LONGITUDE, + ATTR_PERSONS, CONF_ICON, CONF_ID, CONF_LATITUDE, @@ -292,8 +293,8 @@ class Zone(entity.Entity): self.editable = True self._attrs: dict | None = None self._remove_listener: Callable[[], None] | None = None - self._generate_attrs() self._persons_in_zone: set[str] = set() + self._generate_attrs() @classmethod def from_yaml(cls, config: dict) -> Zone: @@ -346,6 +347,7 @@ class Zone(entity.Entity): self._persons_in_zone.remove(person_entity_id) if len(self._persons_in_zone) != cur_count: + self._generate_attrs() self.async_write_ha_state() async def async_added_to_hass(self) -> None: @@ -356,6 +358,7 @@ class Zone(entity.Entity): for person in persons: if self._state_is_in_zone(self.hass.states.get(person)): self._persons_in_zone.add(person) + self._generate_attrs() self.async_on_remove( event.async_track_state_change_filtered( @@ -373,6 +376,7 @@ class Zone(entity.Entity): ATTR_LONGITUDE: self._config[CONF_LONGITUDE], ATTR_RADIUS: self._config[CONF_RADIUS], ATTR_PASSIVE: self._config[CONF_PASSIVE], + ATTR_PERSONS: sorted(self._persons_in_zone), ATTR_EDITABLE: self.editable, } diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index fa87ad954ad..d12c7df6a79 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -12,6 +12,7 @@ from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVers from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import ( EntryControlNotification, + MultilevelSwitchNotification, NotificationNotification, PowerLevelNotification, ) @@ -47,6 +48,7 @@ from .const import ( ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DATA_TYPE, + ATTR_DIRECTION, ATTR_ENDPOINT, ATTR_EVENT, ATTR_EVENT_DATA, @@ -403,7 +405,7 @@ async def async_setup_entry( # noqa: C901 if "notification" not in event: LOGGER.info("Unknown notification: %s", event) return - notification: EntryControlNotification | NotificationNotification | PowerLevelNotification = event[ + notification: EntryControlNotification | NotificationNotification | PowerLevelNotification | MultilevelSwitchNotification = event[ "notification" ] device = dev_reg.async_get_device({get_device_id(client, notification.node)}) @@ -446,6 +448,14 @@ async def async_setup_entry( # noqa: C901 ATTR_ACKNOWLEDGED_FRAMES: notification.acknowledged_frames, } ) + elif isinstance(notification, MultilevelSwitchNotification): + event_data.update( + { + ATTR_COMMAND_CLASS_NAME: "Multilevel Switch", + ATTR_EVENT_TYPE: notification.event_type, + ATTR_DIRECTION: notification.direction, + } + ) else: raise TypeError(f"Unhandled notification type: {notification}") diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 9cd79ecb27b..5608679fe90 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -377,7 +377,7 @@ async def websocket_network_status( }, "controller": { "home_id": controller.home_id, - "library_version": controller.library_version, + "sdk_version": controller.sdk_version, "type": controller.controller_type, "own_node_id": controller.own_node_id, "is_secondary": controller.is_secondary, @@ -386,7 +386,7 @@ async def websocket_network_status( "was_real_primary": controller.was_real_primary, "is_static_update_controller": controller.is_static_update_controller, "is_slave": controller.is_slave, - "serial_api_version": controller.serial_api_version, + "firmware_version": controller.firmware_version, "manufacturer_id": controller.manufacturer_id, "product_id": controller.product_id, "product_type": controller.product_type, diff --git a/homeassistant/components/zwave_js/button.py b/homeassistant/components/zwave_js/button.py index fb62bbdc3dc..ea987b56258 100644 --- a/homeassistant/components/zwave_js/button.py +++ b/homeassistant/components/zwave_js/button.py @@ -60,7 +60,6 @@ class ZWaveNodePingButton(ButtonEntity): async def async_poll_value(self, _: bool) -> None: """Poll a value.""" - # pylint: disable=no-self-use LOGGER.error( "There is no value to refresh for this entity so the zwave_js.refresh_value " "service won't work for it" diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 88c96feea88..188e627bb5e 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -28,22 +28,11 @@ from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - CURRENT_HVAC_COOL, - CURRENT_HVAC_FAN, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, DOMAIN as CLIMATE_DOMAIN, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_NONE, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, + ClimateEntityFeature, + HVACAction, + HVACMode, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -66,36 +55,36 @@ from .helpers import get_value_of_zwave_value # Map Z-Wave HVAC Mode to Home Assistant value # Note: We treat "auto" as "heat_cool" as most Z-Wave devices # report auto_changeover as auto without schedule support. -ZW_HVAC_MODE_MAP: dict[int, str] = { - ThermostatMode.OFF: HVAC_MODE_OFF, - ThermostatMode.HEAT: HVAC_MODE_HEAT, - ThermostatMode.COOL: HVAC_MODE_COOL, +ZW_HVAC_MODE_MAP: dict[int, HVACMode] = { + ThermostatMode.OFF: HVACMode.OFF, + ThermostatMode.HEAT: HVACMode.HEAT, + ThermostatMode.COOL: HVACMode.COOL, # Z-Wave auto mode is actually heat/cool in the hass world - ThermostatMode.AUTO: HVAC_MODE_HEAT_COOL, - ThermostatMode.AUXILIARY: HVAC_MODE_HEAT, - ThermostatMode.FAN: HVAC_MODE_FAN_ONLY, - ThermostatMode.FURNANCE: HVAC_MODE_HEAT, - ThermostatMode.DRY: HVAC_MODE_DRY, - ThermostatMode.AUTO_CHANGE_OVER: HVAC_MODE_HEAT_COOL, - ThermostatMode.HEATING_ECON: HVAC_MODE_HEAT, - ThermostatMode.COOLING_ECON: HVAC_MODE_COOL, - ThermostatMode.AWAY: HVAC_MODE_HEAT_COOL, - ThermostatMode.FULL_POWER: HVAC_MODE_HEAT, + ThermostatMode.AUTO: HVACMode.HEAT_COOL, + ThermostatMode.AUXILIARY: HVACMode.HEAT, + ThermostatMode.FAN: HVACMode.FAN_ONLY, + ThermostatMode.FURNANCE: HVACMode.HEAT, + ThermostatMode.DRY: HVACMode.DRY, + ThermostatMode.AUTO_CHANGE_OVER: HVACMode.HEAT_COOL, + ThermostatMode.HEATING_ECON: HVACMode.HEAT, + ThermostatMode.COOLING_ECON: HVACMode.COOL, + ThermostatMode.AWAY: HVACMode.HEAT_COOL, + ThermostatMode.FULL_POWER: HVACMode.HEAT, } -HVAC_CURRENT_MAP: dict[int, str] = { - ThermostatOperatingState.IDLE: CURRENT_HVAC_IDLE, - ThermostatOperatingState.PENDING_HEAT: CURRENT_HVAC_IDLE, - ThermostatOperatingState.HEATING: CURRENT_HVAC_HEAT, - ThermostatOperatingState.PENDING_COOL: CURRENT_HVAC_IDLE, - ThermostatOperatingState.COOLING: CURRENT_HVAC_COOL, - ThermostatOperatingState.FAN_ONLY: CURRENT_HVAC_FAN, - ThermostatOperatingState.VENT_ECONOMIZER: CURRENT_HVAC_FAN, - ThermostatOperatingState.AUX_HEATING: CURRENT_HVAC_HEAT, - ThermostatOperatingState.SECOND_STAGE_HEATING: CURRENT_HVAC_HEAT, - ThermostatOperatingState.SECOND_STAGE_COOLING: CURRENT_HVAC_COOL, - ThermostatOperatingState.SECOND_STAGE_AUX_HEAT: CURRENT_HVAC_HEAT, - ThermostatOperatingState.THIRD_STAGE_AUX_HEAT: CURRENT_HVAC_HEAT, +HVAC_CURRENT_MAP: dict[int, HVACAction] = { + ThermostatOperatingState.IDLE: HVACAction.IDLE, + ThermostatOperatingState.PENDING_HEAT: HVACAction.IDLE, + ThermostatOperatingState.HEATING: HVACAction.HEATING, + ThermostatOperatingState.PENDING_COOL: HVACAction.IDLE, + ThermostatOperatingState.COOLING: HVACAction.COOLING, + ThermostatOperatingState.FAN_ONLY: HVACAction.FAN, + ThermostatOperatingState.VENT_ECONOMIZER: HVACAction.FAN, + ThermostatOperatingState.AUX_HEATING: HVACAction.HEATING, + ThermostatOperatingState.SECOND_STAGE_HEATING: HVACAction.HEATING, + ThermostatOperatingState.SECOND_STAGE_COOLING: HVACAction.COOLING, + ThermostatOperatingState.SECOND_STAGE_AUX_HEAT: HVACAction.HEATING, + ThermostatOperatingState.THIRD_STAGE_AUX_HEAT: HVACAction.HEATING, } ATTR_FAN_STATE = "fan_state" @@ -137,7 +126,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): ) -> None: """Initialize thermostat.""" super().__init__(config_entry, client, info) - self._hvac_modes: dict[str, int | None] = {} + self._hvac_modes: dict[HVACMode, int | None] = {} self._hvac_presets: dict[str, int | None] = {} self._unit_value: ZwaveValue | None = None @@ -193,17 +182,19 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): check_all_endpoints=True, ) self._set_modes_and_presets() - self._supported_features = 0 + self._attr_supported_features = 0 if len(self._hvac_presets) > 1: - self._supported_features |= SUPPORT_PRESET_MODE + self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE # If any setpoint value exists, we can assume temperature # can be set if any(self._setpoint_values.values()): - self._supported_features |= SUPPORT_TARGET_TEMPERATURE - if HVAC_MODE_HEAT_COOL in self.hvac_modes: - self._supported_features |= SUPPORT_TARGET_TEMPERATURE_RANGE + self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE + if HVACMode.HEAT_COOL in self.hvac_modes: + self._attr_supported_features |= ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) if self._fan_mode: - self._supported_features |= SUPPORT_FAN_MODE + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: """Optionally return a ZwaveValue for a setpoint.""" @@ -214,7 +205,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): def _set_modes_and_presets(self) -> None: """Convert Z-Wave Thermostat modes into Home Assistant modes and presets.""" - all_modes: dict[str, int | None] = {} + all_modes: dict[HVACMode, int | None] = {} all_presets: dict[str, int | None] = {PRESET_NONE: None} # Z-Wave uses one list for both modes and presets. @@ -261,23 +252,23 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return PRECISION_TENTHS @property - def hvac_mode(self) -> str: + def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" if self._current_mode is None: # Thermostat(valve) with no support for setting a mode is considered heating-only - return HVAC_MODE_HEAT + return HVACMode.HEAT if self._current_mode.value is None: # guard missing value - return HVAC_MODE_HEAT - return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVAC_MODE_HEAT_COOL) + return HVACMode.HEAT + return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVACMode.HEAT_COOL) @property - def hvac_modes(self) -> list[str]: + def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" return list(self._hvac_modes) @property - def hvac_action(self) -> str | None: + def hvac_action(self) -> HVACAction | None: """Return the current running hvac operation if supported.""" if not self._operating_state: return None @@ -382,11 +373,6 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): return None - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return self._supported_features - @property def min_temp(self) -> float: """Return the minimum temperature.""" @@ -439,7 +425,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - hvac_mode: str | None = kwargs.get(ATTR_HVAC_MODE) + hvac_mode: HVACMode | None = kwargs.get(ATTR_HVAC_MODE) if hvac_mode is not None: await self.async_set_hvac_mode(hvac_mode) @@ -464,7 +450,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): if target_temp_high is not None: await self.info.node.async_set_value(setpoint_high, target_temp_high) - async def async_set_hvac_mode(self, hvac_mode: str) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" if (hvac_mode_id := self._hvac_modes.get(hvac_mode)) is None: raise ValueError(f"Received an invalid hvac mode: {hvac_mode}") diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 66cb91f9330..60807471e03 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -14,6 +14,7 @@ from zwave_js_server.version import VersionInfo, get_server_version from homeassistant import config_entries, exceptions from homeassistant.components import usb from homeassistant.components.hassio import HassioServiceInfo, is_hassio +from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.const import CONF_NAME, CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import ( @@ -337,6 +338,33 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_manual() + async def async_step_zeroconf( + self, discovery_info: ZeroconfServiceInfo + ) -> FlowResult: + """Handle zeroconf discovery.""" + home_id = str(discovery_info.properties["homeId"]) + await self.async_set_unique_id(home_id) + self._abort_if_unique_id_configured() + self.ws_address = f"ws://{discovery_info.host}:{discovery_info.port}" + self.context.update({"title_placeholders": {CONF_NAME: home_id}}) + return await self.async_step_zeroconf_confirm() + + async def async_step_zeroconf_confirm( + self, user_input: dict | None = None + ) -> FlowResult: + """Confirm the setup.""" + if user_input is not None: + return await self.async_step_manual({CONF_URL: self.ws_address}) + + assert self.ws_address + return self.async_show_form( + step_id="zeroconf_confirm", + description_placeholders={ + "home_id": self.unique_id, + CONF_URL: self.ws_address[5:], + }, + ) + async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: """Handle USB Discovery.""" if not is_hassio(self.hass): diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index d6d63487b8a..1fd8e3e9d14 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -45,6 +45,7 @@ ATTR_PROPERTY_KEY_NAME = "property_key_name" ATTR_PROPERTY = "property" ATTR_PROPERTY_KEY = "property_key" ATTR_PARAMETERS = "parameters" +ATTR_DIRECTION = "direction" ATTR_EVENT = "event" ATTR_EVENT_LABEL = "event_label" ATTR_EVENT_TYPE = "event_type" @@ -70,15 +71,16 @@ ATTR_CONFIG_ENTRY_ID = "config_entry_id" ATTR_PARTIAL_DICT_MATCH = "partial_dict_match" # service constants -SERVICE_SET_LOCK_USERCODE = "set_lock_usercode" +SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS = "bulk_set_partial_config_parameters" SERVICE_CLEAR_LOCK_USERCODE = "clear_lock_usercode" -SERVICE_SET_VALUE = "set_value" -SERVICE_RESET_METER = "reset_meter" +SERVICE_INVOKE_CC_API = "invoke_cc_api" SERVICE_MULTICAST_SET_VALUE = "multicast_set_value" SERVICE_PING = "ping" SERVICE_REFRESH_VALUE = "refresh_value" +SERVICE_RESET_METER = "reset_meter" SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" -SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS = "bulk_set_partial_config_parameters" +SERVICE_SET_LOCK_USERCODE = "set_lock_usercode" +SERVICE_SET_VALUE = "set_value" ATTR_NODES = "nodes" # config parameter @@ -92,6 +94,9 @@ ATTR_BROADCAST = "broadcast" # meter reset ATTR_METER_TYPE = "meter_type" ATTR_METER_TYPE_NAME = "meter_type_name" +# invoke CC API +ATTR_METHOD_NAME = "method_name" +ATTR_PARAMETERS = "parameters" ADDON_SLUG = "core_zwave_js" diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index dc72b545396..9281f0bc21c 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -21,15 +21,9 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN as COVER_DOMAIN, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, CoverDeviceClass, CoverEntity, + CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -180,13 +174,13 @@ class ZWaveTiltCover(ZWaveCover): """Representation of a Z-Wave Cover device with tilt.""" _attr_supported_features = ( - SUPPORT_OPEN - | SUPPORT_CLOSE - | SUPPORT_STOP - | SUPPORT_SET_POSITION - | SUPPORT_OPEN_TILT - | SUPPORT_CLOSE_TILT - | SUPPORT_SET_TILT_POSITION + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION ) def __init__( @@ -231,7 +225,7 @@ class ZWaveTiltCover(ZWaveCover): class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity): """Representation of a Z-Wave motorized barrier device.""" - _attr_supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE _attr_device_class = CoverDeviceClass.GARAGE def __init__( diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index f323ee0f5f9..dfb6661b5c0 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -15,13 +15,16 @@ from homeassistant.components.diagnostics.util import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.device_registry import DeviceEntry -from homeassistant.helpers.entity_registry import async_entries_for_device, async_get from .const import DATA_CLIENT, DOMAIN -from .helpers import ZwaveValueID, get_home_and_node_id_from_device_entry +from .helpers import ( + ZwaveValueID, + get_home_and_node_id_from_device_entry, + get_state_key_from_unique_id, + get_value_id_from_unique_id, +) KEYS_TO_REDACT = {"homeId", "location"} @@ -36,7 +39,7 @@ def redact_value_of_zwave_value(zwave_value: ValueDataType) -> ValueDataType: zwave_value_id = ZwaveValueID( property_=zwave_value["property"], command_class=CommandClass(zwave_value["commandClass"]), - endpoint=zwave_value["endpoint"], + endpoint=zwave_value.get("endpoint"), property_key=zwave_value.get("propertyKey"), ) if all( @@ -61,28 +64,19 @@ def redact_node_state(node_state: NodeDataType) -> NodeDataType: def get_device_entities( - hass: HomeAssistant, node: Node, device: DeviceEntry + hass: HomeAssistant, node: Node, device: dr.DeviceEntry ) -> list[dict[str, Any]]: """Get entities for a device.""" - entity_entries = async_entries_for_device( - async_get(hass), device.id, include_disabled_entities=True + entity_entries = er.async_entries_for_device( + er.async_get(hass), device.id, include_disabled_entities=True ) entities = [] for entry in entity_entries: - state_key = None - split_unique_id = entry.unique_id.split(".") - # If the unique ID has three parts, it's either one of the generic per node - # entities (node status sensor, ping button) or a binary sensor for a particular - # state. If we can get the state key, we will add it to the dictionary. - if len(split_unique_id) == 3: - try: - state_key = int(split_unique_id[-1]) - # If the third part of the unique ID isn't a state key, the entity must be a - # generic entity. We won't add those since they won't help with - # troubleshooting. - except ValueError: - continue - value_id = split_unique_id[1] + # If the value ID returns as None, we don't need to include this entity + if (value_id := get_value_id_from_unique_id(entry.unique_id)) is None: + continue + state_key = get_state_key_from_unique_id(entry.unique_id) + zwave_value = node.values[value_id] primary_value_data = { "command_class": zwave_value.command_class, diff --git a/homeassistant/components/zwave_js/discovery_data_template.py b/homeassistant/components/zwave_js/discovery_data_template.py index 622a8222fa7..9dc90a43f3d 100644 --- a/homeassistant/components/zwave_js/discovery_data_template.py +++ b/homeassistant/components/zwave_js/discovery_data_template.py @@ -240,7 +240,6 @@ class BaseDiscoverySchemaDataTemplate: Can optionally be implemented by subclasses if input data needs to be transformed once discovered Value is available. """ - # pylint: disable=no-self-use return {} def values_to_watch(self, resolved_data: Any) -> Iterable[ZwaveValue]: @@ -249,7 +248,6 @@ class BaseDiscoverySchemaDataTemplate: Should be implemented by subclasses only if there are values to watch. """ - # pylint: disable=no-self-use return [] def value_ids_to_watch(self, resolved_data: Any) -> set[str]: diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index 21c45bbbf42..623ee072f3a 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -14,9 +14,8 @@ from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.fan import ( DOMAIN as FAN_DOMAIN, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, FanEntity, + FanEntityFeature, NotValidPresetModeError, ) from homeassistant.config_entries import ConfigEntry @@ -74,6 +73,8 @@ async def async_setup_entry( class ZwaveFan(ZWaveBaseEntity, FanEntity): """Representation of a Z-Wave fan.""" + _attr_supported_features = FanEntityFeature.SET_SPEED + def __init__( self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo ) -> None: @@ -139,11 +140,6 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): """Return the number of speeds the fan supports.""" return int_states_in_range(DEFAULT_SPEED_RANGE) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return SUPPORT_SET_SPEED - class ValueMappingZwaveFan(ZwaveFan): """A Zwave fan with a value mapping data (e.g., 1-24 is low).""" @@ -239,9 +235,9 @@ class ValueMappingZwaveFan(ZwaveFan): @property def supported_features(self) -> int: """Flag supported features.""" - flags = SUPPORT_SET_SPEED + flags: int = FanEntityFeature.SET_SPEED if self.has_fan_value_mapping and self.fan_value_mapping.presets: - flags |= SUPPORT_PRESET_MODE + flags |= FanEntityFeature.PRESET_MODE return flags @@ -376,7 +372,7 @@ class ZwaveThermostatFan(ZWaveBaseEntity, FanEntity): @property def supported_features(self) -> int: """Flag supported features.""" - return SUPPORT_PRESET_MODE + return FanEntityFeature.PRESET_MODE @property def fan_state(self) -> str | None: diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 35f278d4571..a5a2da23206 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import astuple, dataclass +import logging from typing import Any, cast import voluptuous as vol @@ -57,6 +58,34 @@ class ZwaveValueID: raise ValueError("At least one of the fields must be set.") +@callback +def get_value_id_from_unique_id(unique_id: str) -> str | None: + """ + Get the value ID and optional state key from a unique ID. + + Raises ValueError + """ + split_unique_id = unique_id.split(".") + # If the unique ID contains a `-` in its second part, the unique ID contains + # a value ID and we can return it. + if "-" in (value_id := split_unique_id[1]): + return value_id + return None + + +@callback +def get_state_key_from_unique_id(unique_id: str) -> int | None: + """Get the state key from a unique ID.""" + # If the unique ID has more than two parts, it's a special unique ID. If the last + # part of the unique ID is an int, then it's a state key and we return it. + if len(split_unique_id := unique_id.split(".")) > 2: + try: + return int(split_unique_id[-1]) + except ValueError: + pass + return None + + @callback def get_value_of_zwave_value(value: ZwaveValue | None) -> Any | None: """Return the value of a ZwaveValue.""" @@ -251,6 +280,7 @@ def async_get_nodes_from_targets( val: dict[str, Any], ent_reg: er.EntityRegistry | None = None, dev_reg: dr.DeviceRegistry | None = None, + logger: logging.Logger = LOGGER, ) -> set[ZwaveNode]: """ Get nodes for all targets. @@ -263,7 +293,7 @@ def async_get_nodes_from_targets( try: nodes.add(async_get_node_from_entity_id(hass, entity_id, ent_reg, dev_reg)) except ValueError as err: - LOGGER.warning(err.args[0]) + logger.warning(err.args[0]) # Convert all area IDs to nodes for area_id in val.get(ATTR_AREA_ID, []): @@ -274,7 +304,7 @@ def async_get_nodes_from_targets( try: nodes.add(async_get_node_from_device_id(hass, device_id, dev_reg)) except ValueError as err: - LOGGER.warning(err.args[0]) + logger.warning(err.args[0]) return nodes diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index 0f3f17df41a..ca51bc83986 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -30,13 +30,10 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_RGBW_COLOR, ATTR_TRANSITION, - COLOR_MODE_BRIGHTNESS, - COLOR_MODE_COLOR_TEMP, - COLOR_MODE_HS, - COLOR_MODE_RGBW, DOMAIN as LIGHT_DOMAIN, - SUPPORT_TRANSITION, + ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -141,13 +138,13 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): self._calculate_color_values() if self._supports_rgbw: - self._supported_color_modes.add(COLOR_MODE_RGBW) + self._supported_color_modes.add(ColorMode.RGBW) elif self._supports_color: - self._supported_color_modes.add(COLOR_MODE_HS) + self._supported_color_modes.add(ColorMode.HS) if self._supports_color_temp: - self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + self._supported_color_modes.add(ColorMode.COLOR_TEMP) if not self._supported_color_modes: - self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS) + self._supported_color_modes.add(ColorMode.BRIGHTNESS) # Entity class attributes self._attr_supported_features = 0 @@ -163,7 +160,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): ) if self.supports_brightness_transition or self.supports_color_transition: - self._attr_supported_features |= SUPPORT_TRANSITION + self._attr_supported_features |= LightEntityFeature.TRANSITION @callback def on_value_update(self) -> None: @@ -392,7 +389,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): multi_color = {} # Default: Brightness (no color) - self._color_mode = COLOR_MODE_BRIGHTNESS + self._color_mode = ColorMode.BRIGHTNESS # RGB support if red_val and green_val and blue_val: @@ -405,7 +402,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # convert to HS self._hs_color = color_util.color_RGB_to_hs(red, green, blue) # Light supports color, set color mode to hs - self._color_mode = COLOR_MODE_HS + self._color_mode = ColorMode.HS # color temperature support if ww_val and cw_val: @@ -419,7 +416,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): - ((cold_white / 255) * (self._max_mireds - self._min_mireds)) ) # White channels turned on, set color mode to color_temp - self._color_mode = COLOR_MODE_COLOR_TEMP + self._color_mode = ColorMode.COLOR_TEMP else: self._color_temp = None # only one white channel (warm white) = rgbw support @@ -428,14 +425,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): white = multi_color.get(COLOR_SWITCH_COMBINED_WARM_WHITE, ww_val.value) self._rgbw_color = (red, green, blue, white) # Light supports rgbw, set color mode to rgbw - self._color_mode = COLOR_MODE_RGBW + self._color_mode = ColorMode.RGBW # only one white channel (cool white) = rgbw support elif cw_val: self._supports_rgbw = True white = multi_color.get(COLOR_SWITCH_COMBINED_COLD_WHITE, cw_val.value) self._rgbw_color = (red, green, blue, white) # Light supports rgbw, set color mode to rgbw - self._color_mode = COLOR_MODE_RGBW + self._color_mode = ColorMode.RGBW class ZwaveBlackIsOffLight(ZwaveLight): @@ -452,7 +449,7 @@ class ZwaveBlackIsOffLight(ZwaveLight): super().__init__(config_entry, client, info) self._last_color: dict[str, int] | None = None - self._supported_color_modes.discard(COLOR_MODE_BRIGHTNESS) + self._supported_color_modes.discard(ColorMode.BRIGHTNESS) @property def brightness(self) -> int: diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index abdb5d6fbb8..934c3f9a3f5 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.35.3"], + "requirements": ["zwave-js-server-python==0.36.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["usb", "http", "websocket_api"], "iot_class": "local_push", @@ -29,5 +29,6 @@ ] } ], + "zeroconf": ["_zwave-js-server._tcp.local."], "loggers": ["zwave_js_server"] } diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 8ac909d76de..e60a0793608 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -487,7 +487,6 @@ class ZWaveNodeStatusSensor(SensorEntity): async def async_poll_value(self, _: bool) -> None: """Poll a value.""" - # pylint: disable=no-self-use LOGGER.error( "There is no value to refresh for this entity so the zwave_js.refresh_value " "service won't work for it" diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index 2d31bed108f..70099859d39 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -3,12 +3,13 @@ from __future__ import annotations import asyncio import logging -from typing import Any +from typing import Any, cast import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.const import CommandStatus -from zwave_js_server.exceptions import SetValueFailed +from zwave_js_server.const import CommandClass, CommandStatus +from zwave_js_server.exceptions import FailedCommand, SetValueFailed +from zwave_js_server.model.endpoint import Endpoint from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import get_value_id from zwave_js_server.util.multicast import async_multicast_set_value @@ -20,13 +21,20 @@ from zwave_js_server.util.node import ( from homeassistant.components.group import expand_entity_ids from homeassistant.const import ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from . import const from .config_validation import BITMASK_SCHEMA, VALUE_SCHEMA -from .helpers import async_get_nodes_from_targets +from .helpers import ( + async_get_node_from_device_id, + async_get_node_from_entity_id, + async_get_nodes_from_area_id, + async_get_nodes_from_targets, + get_value_id_from_unique_id, +) _LOGGER = logging.getLogger(__name__) @@ -78,7 +86,7 @@ class ZWaveServices: def get_nodes_from_service_data(val: dict[str, Any]) -> dict[str, Any]: """Get nodes set from service data.""" val[const.ATTR_NODES] = async_get_nodes_from_targets( - self._hass, val, self._ent_reg, self._dev_reg + self._hass, val, self._ent_reg, self._dev_reg, _LOGGER ) return val @@ -132,8 +140,8 @@ class ZWaveServices: for entity_id in val[ATTR_ENTITY_ID]: entry = self._ent_reg.async_get(entity_id) if entry is None or entry.platform != const.DOMAIN: - const.LOGGER.info( - "Entity %s is not a valid %s entity.", entity_id, const.DOMAIN + _LOGGER.info( + "Entity %s is not a valid %s entity", entity_id, const.DOMAIN ) invalid_entities.append(entity_id) @@ -326,9 +334,38 @@ class ZWaveServices: ), ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_INVOKE_CC_API, + self.async_invoke_cc_api, + schema=vol.Schema( + vol.All( + { + vol.Optional(ATTR_AREA_ID): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(ATTR_DEVICE_ID): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(const.ATTR_COMMAND_CLASS): vol.All( + vol.Coerce(int), vol.Coerce(CommandClass) + ), + vol.Optional(const.ATTR_ENDPOINT): vol.Coerce(int), + vol.Required(const.ATTR_METHOD_NAME): cv.string, + vol.Required(const.ATTR_PARAMETERS): list, + }, + cv.has_at_least_one_key( + ATTR_DEVICE_ID, ATTR_ENTITY_ID, ATTR_AREA_ID + ), + get_nodes_from_service_data, + has_at_least_one_node, + ), + ), + ) + async def async_set_config_parameter(self, service: ServiceCall) -> None: """Set a config value on a node.""" - # pylint: disable=no-self-use nodes = service.data[const.ATTR_NODES] property_or_property_name = service.data[const.ATTR_CONFIG_PARAMETER] property_key = service.data.get(const.ATTR_CONFIG_PARAMETER_BITMASK) @@ -356,7 +393,6 @@ class ZWaveServices: self, service: ServiceCall ) -> None: """Bulk set multiple partial config values on a node.""" - # pylint: disable=no-self-use nodes = service.data[const.ATTR_NODES] property_ = service.data[const.ATTR_CONFIG_PARAMETER] new_value = service.data[const.ATTR_CONFIG_VALUE] @@ -391,7 +427,6 @@ class ZWaveServices: async def async_set_value(self, service: ServiceCall) -> None: """Set a value on a node.""" - # pylint: disable=no-self-use nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] command_class = service.data[const.ATTR_COMMAND_CLASS] property_ = service.data[const.ATTR_PROPERTY] @@ -428,11 +463,11 @@ class ZWaveServices: ) if success is False: - raise SetValueFailed( + raise HomeAssistantError( "Unable to set value, refer to " "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue " "for possible reasons" - ) + ) from SetValueFailed async def async_multicast_set_value(self, service: ServiceCall) -> None: """Set a value via multicast to multiple nodes.""" @@ -441,7 +476,7 @@ class ZWaveServices: options = service.data.get(const.ATTR_OPTIONS) if not broadcast and len(nodes) == 1: - const.LOGGER.info( + _LOGGER.info( "Passing the zwave_js.multicast_set_value service call to the " "zwave_js.set_value service since only one node was targeted" ) @@ -499,15 +534,96 @@ class ZWaveServices: ) if success is False: - raise SetValueFailed("Unable to set value via multicast") + raise HomeAssistantError( + "Unable to set value via multicast" + ) from SetValueFailed async def async_ping(self, service: ServiceCall) -> None: """Ping node(s).""" # pylint: disable=no-self-use - const.LOGGER.warning( + _LOGGER.warning( "This service is deprecated in favor of the ping button entity. Service " "calls will still work for now but the service will be removed in a " "future release" ) nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] await asyncio.gather(*(node.async_ping() for node in nodes)) + + async def async_invoke_cc_api(self, service: ServiceCall) -> None: + """Invoke a command class API.""" + command_class: CommandClass = service.data[const.ATTR_COMMAND_CLASS] + method_name: str = service.data[const.ATTR_METHOD_NAME] + parameters: list[Any] = service.data[const.ATTR_PARAMETERS] + + async def _async_invoke_cc_api(endpoints: set[Endpoint]) -> None: + """Invoke the CC API on a node endpoint.""" + errors: list[str] = [] + for endpoint in endpoints: + _LOGGER.info( + "Invoking %s CC API method %s on endpoint %s", + command_class.name, + method_name, + endpoint, + ) + try: + await endpoint.async_invoke_cc_api( + command_class, method_name, *parameters + ) + except FailedCommand as err: + errors.append(cast(str, err.args[0])) + if errors: + raise HomeAssistantError( + "\n".join([f"{len(errors)} error(s):", *errors]) + ) + + # If an endpoint is provided, we assume the user wants to call the CC API on + # that endpoint for all target nodes + if (endpoint := service.data.get(const.ATTR_ENDPOINT)) is not None: + await _async_invoke_cc_api( + {node.endpoints[endpoint] for node in service.data[const.ATTR_NODES]} + ) + return + + # If no endpoint is provided, we target endpoint 0 for all device and area + # nodes and we target the endpoint of the primary value for all entities + # specified. + endpoints: set[Endpoint] = set() + for area_id in service.data.get(ATTR_AREA_ID, []): + for node in async_get_nodes_from_area_id( + self._hass, area_id, self._ent_reg, self._dev_reg + ): + endpoints.add(node.endpoints[0]) + + for device_id in service.data.get(ATTR_DEVICE_ID, []): + try: + node = async_get_node_from_device_id( + self._hass, device_id, self._dev_reg + ) + except ValueError as err: + _LOGGER.warning(err.args[0]) + continue + endpoints.add(node.endpoints[0]) + + for entity_id in service.data.get(ATTR_ENTITY_ID, []): + if ( + not (entity_entry := self._ent_reg.async_get(entity_id)) + or entity_entry.platform != const.DOMAIN + ): + _LOGGER.warning( + "Skipping entity %s as it is not a valid %s entity", + entity_id, + const.DOMAIN, + ) + continue + node = async_get_node_from_entity_id( + self._hass, entity_id, self._ent_reg, self._dev_reg + ) + if ( + value_id := get_value_id_from_unique_id(entity_entry.unique_id) + ) is None: + _LOGGER.warning("Skipping entity %s as it has no value ID", entity_id) + continue + + endpoints.add(node.endpoints[node.values[value_id].endpoint]) + + await _async_invoke_cc_api(endpoints) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index 206af776a61..eccd46745a3 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -252,3 +252,39 @@ reset_meter: required: false selector: text: + +invoke_cc_api: + name: Invoke a Command Class API on a node (Advanced) + description: Allows for calling a Command Class API on a node. Some Command Classes can't be fully controlled via the `set_value` service and require direct calls to the Command Class API. + target: + entity: + integration: zwave_js + fields: + command_class: + name: Command Class + description: The ID of the command class that you want to issue a command to. + example: 132 + required: true + selector: + text: + endpoint: + name: Endpoint + description: The endpoint to call the API on. If an endpoint is specified, that endpoint will be targeted for all nodes associated with the target areas, devices, and/or entities. If an endpoint is not specified, the root endpoint (0) will be targeted for nodes associated with target areas and devices, and the endpoint for the primary value of each entity will be targeted. + example: 1 + required: false + selector: + text: + method_name: + name: Method Name + description: The name of the API method to call. Refer to the Z-Wave JS Command Class API documentation (https://zwave-js.github.io/node-zwave-js/#/api/CCs/index) for available methods. + example: setInterval + required: true + selector: + text: + parameters: + name: Parameters + description: A list of parameters to pass to the API method. Refer to the Z-Wave JS Command Class API documentation (https://zwave-js.github.io/node-zwave-js/#/api/CCs/index) for parameters. + example: [1, 1] + required: true + selector: + object: diff --git a/homeassistant/components/zwave_js/siren.py b/homeassistant/components/zwave_js/siren.py index a3fe3dfd595..e7443f33dae 100644 --- a/homeassistant/components/zwave_js/siren.py +++ b/homeassistant/components/zwave_js/siren.py @@ -6,15 +6,12 @@ from typing import Any from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const.command_class.sound_switch import ToneID -from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN, SirenEntity -from homeassistant.components.siren.const import ( - ATTR_TONE, - ATTR_VOLUME_LEVEL, - SUPPORT_TONES, - SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, - SUPPORT_VOLUME_SET, +from homeassistant.components.siren import ( + DOMAIN as SIREN_DOMAIN, + SirenEntity, + SirenEntityFeature, ) +from homeassistant.components.siren.const import ATTR_TONE, ATTR_VOLUME_LEVEL from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -62,10 +59,12 @@ class ZwaveSirenEntity(ZWaveBaseEntity, SirenEntity): int(id): val for id, val in self.info.primary_value.metadata.states.items() } self._attr_supported_features = ( - SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_SET + SirenEntityFeature.TURN_ON + | SirenEntityFeature.TURN_OFF + | SirenEntityFeature.VOLUME_SET ) if self._attr_available_tones: - self._attr_supported_features |= SUPPORT_TONES + self._attr_supported_features |= SirenEntityFeature.TONES @property def is_on(self) -> bool | None: diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 13f65921cdb..91a6eae6ab6 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -36,6 +36,10 @@ }, "hassio_confirm": { "title": "Set up Z-Wave JS integration with the Z-Wave JS add-on" + }, + "zeroconf_confirm": { + "description": "Do you want to add the Z-Wave JS Server with home ID {home_id} found at {url} to Home Assistant?", + "title": "Discovered Z-Wave JS Server" } }, "error": { diff --git a/homeassistant/components/zwave_js/translations/bg.json b/homeassistant/components/zwave_js/translations/bg.json index bdae86569ac..59fcf968740 100644 --- a/homeassistant/components/zwave_js/translations/bg.json +++ b/homeassistant/components/zwave_js/translations/bg.json @@ -17,6 +17,9 @@ "data": { "url": "URL" } + }, + "zeroconf_confirm": { + "title": "\u041e\u0442\u043a\u0440\u0438\u0442 Z-Wave JS \u0441\u044a\u0440\u0432\u044a\u0440" } } }, diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json index 5e0f834ef19..6691894b79d 100644 --- a/homeassistant/components/zwave_js/translations/ca.json +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Vols configurar {name} amb el complement Z-Wave JS?" + }, + "zeroconf_confirm": { + "description": "Vols afegir el servidor Z-Wave JS amb ID {home_id} que es troba a {url} a Home Assistant?", + "title": "Servidor Z-Wave JS descobert" } } }, diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index 4900c9055d9..02418f44306 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "M\u00f6chtest du {name} mit dem Z-Wave JS Add-on einrichten?" + }, + "zeroconf_confirm": { + "description": "M\u00f6chtest du den Z-Wave JS-Server mit der Home-ID {home_id} , gefunden unter {url} , zu Home Assistant hinzuf\u00fcgen?", + "title": "Z-Wave JS-Server entdeckt" } } }, diff --git a/homeassistant/components/zwave_js/translations/el.json b/homeassistant/components/zwave_js/translations/el.json index 2a4a519369a..66330206b60 100644 --- a/homeassistant/components/zwave_js/translations/el.json +++ b/homeassistant/components/zwave_js/translations/el.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {name} \u03bc\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf Z-Wave JS;" + }, + "zeroconf_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Z-Wave JS Server \u03bc\u03b5 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03c3\u03c0\u03b9\u03c4\u03b9\u03bf\u03cd {home_id} \u03c0\u03bf\u03c5 \u03b2\u03c1\u03af\u03c3\u03ba\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf {url} \u03c3\u03c4\u03bf Home Assistant;", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 Z-Wave JS" } } }, diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 3962674ff9c..fa9794ed847 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Do you want to setup {name} with the Z-Wave JS add-on?" + }, + "zeroconf_confirm": { + "description": "Do you want to add the Z-Wave JS Server with home ID {home_id} found at {url} to Home Assistant?", + "title": "Discovered Z-Wave JS Server" } } }, diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json index c6c5db1ebd7..29f50ccb290 100644 --- a/homeassistant/components/zwave_js/translations/et.json +++ b/homeassistant/components/zwave_js/translations/et.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Kas seadistada Z-Wave JS lisandmoodul {name}?" + }, + "zeroconf_confirm": { + "description": "Kas lisada Z-Wave JS-server, mille ID {home_id} on leitud aadressil {url}, Home Assistant'ile?", + "title": "Avastatud Z-Wave JS Server" } } }, diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 2645e573b6d..22e8ed19e32 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Voulez-vous configurer {name} avec le plugin Z-Wave JS ?" + }, + "zeroconf_confirm": { + "description": "Voulez-vous ajouter le serveur Z-Wave\u00a0JS portant l'ID {home_id} et trouv\u00e9 sur {url} \u00e0 Home Assistant\u00a0?", + "title": "Serveur Z-Wave\u00a0JS d\u00e9couvert" } } }, diff --git a/homeassistant/components/zwave_js/translations/hu.json b/homeassistant/components/zwave_js/translations/hu.json index 1803cfa4a26..07dbb93b703 100644 --- a/homeassistant/components/zwave_js/translations/hu.json +++ b/homeassistant/components/zwave_js/translations/hu.json @@ -7,7 +7,7 @@ "addon_set_config_failed": "Nem siker\u00fclt be\u00e1ll\u00edtani a Z-Wave JS konfigur\u00e1ci\u00f3t.", "addon_start_failed": "Nem siker\u00fclt elind\u00edtani a Z-Wave JS b\u0151v\u00edtm\u00e9nyt.", "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van", + "already_in_progress": "A be\u00e1ll\u00edt\u00e1si folyamat m\u00e1r el lett kezdve", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "discovery_requires_supervisor": "A felfedez\u00e9shez a fel\u00fcgyel\u0151re van sz\u00fcks\u00e9g.", "not_zwave_device": "A felfedezett eszk\u00f6z nem Z-Wave eszk\u00f6z." @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Szeretn\u00e9 be\u00e1ll\u00edtani {name} alkalmaz\u00e1st a Z-Wave JS b\u0151v\u00edtm\u00e9nnyel?" + }, + "zeroconf_confirm": { + "description": "Szeretn\u00e9 hozz\u00e1adni a {url} c\u00edmen tal\u00e1lhat\u00f3 {home_id} azonos\u00edt\u00f3val rendelkez\u0151 Z-Wave JS szervert a Home Assistanthoz?", + "title": "Felfedezett Z-Wave JS szerver" } } }, diff --git a/homeassistant/components/zwave_js/translations/id.json b/homeassistant/components/zwave_js/translations/id.json index 7ffb9d7b713..ef14a550210 100644 --- a/homeassistant/components/zwave_js/translations/id.json +++ b/homeassistant/components/zwave_js/translations/id.json @@ -10,7 +10,7 @@ "already_in_progress": "Alur konfigurasi sedang berlangsung", "cannot_connect": "Gagal terhubung", "discovery_requires_supervisor": "Fitur penemuan membutuhkan supervisor.", - "not_zwave_device": "Perangkat yang ditemukan bukanperangkat Z-Wave." + "not_zwave_device": "Perangkat yang ditemukan bukan perangkat Z-Wave." }, "error": { "addon_start_failed": "Gagal memulai add-on Z-Wave JS. Periksa konfigurasi.", @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Ingin menyiapkan {name} dengan add-on Z-Wave JS?" + }, + "zeroconf_confirm": { + "description": "Apakah Anda ingin menambahkan Server Z-Wave JS dengan ID rumah {home_id} di {url} ke Home Assistant?", + "title": "Server Z-Wave JS yang Ditemukan" } } }, diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index d455b7befe6..5922601921a 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Vuoi configurare {name} con il componente aggiuntivo JS Z-Wave?" + }, + "zeroconf_confirm": { + "description": "Vuoi aggiungere il server Z-Wave JS con l'ID casa {home_id} trovato in {url} a Home Assistant?", + "title": "Server JS Z-Wave rilevato" } } }, diff --git a/homeassistant/components/zwave_js/translations/ja.json b/homeassistant/components/zwave_js/translations/ja.json index c85025c1394..6df591dd0ab 100644 --- a/homeassistant/components/zwave_js/translations/ja.json +++ b/homeassistant/components/zwave_js/translations/ja.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Z-Wave JS\u30a2\u30c9\u30aa\u30f3\u3067 {name} \u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" + }, + "zeroconf_confirm": { + "description": "{url} \u306b\u3042\u308b\u30db\u30fc\u30e0ID {home_id} \u306eZ-Wave JS\u30b5\u30fc\u30d0\u30fc\u3092Home Assistant\u306b\u8ffd\u52a0\u3057\u307e\u3059\u304b\uff1f", + "title": "Z-Wave JS\u30b5\u30fc\u30d0\u30fc\u3092\u767a\u898b" } } }, diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index 1f99373c18f..2d3ce5421ed 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Wilt u {name} instellen met de Z-Wave JS add-on?" + }, + "zeroconf_confirm": { + "description": "Wilt u de Z-Wave JS Server met home ID {home_id} gevonden op {url} toevoegen aan Home Assistant?", + "title": "Ontdekt Z-Wave JS Server" } } }, diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index 24efdf1c573..854bd307abc 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Vil du konfigurere {name} med Z-Wave JS-tillegget?" + }, + "zeroconf_confirm": { + "description": "Vil du legge til Z-Wave JS Server med hjemme-ID {home_id} funnet p\u00e5 {url} til Home Assistant?", + "title": "Oppdaget Z-Wave JS Server" } } }, diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json index 68dd6555552..2edb9fd8c1f 100644 --- a/homeassistant/components/zwave_js/translations/pl.json +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Czy chcesz skonfigurowa\u0107 {name} z dodatkiem Z-Wave JS?" + }, + "zeroconf_confirm": { + "description": "Czy chcesz doda\u0107 do Home Assistanta serwer Z-Wave JS z identyfikatorem {home_id} znalezionym pod {url}?", + "title": "Wykryty serwer Z-Wave JS" } } }, diff --git a/homeassistant/components/zwave_js/translations/pt-BR.json b/homeassistant/components/zwave_js/translations/pt-BR.json index b8fadd65089..5e1e8610fc7 100644 --- a/homeassistant/components/zwave_js/translations/pt-BR.json +++ b/homeassistant/components/zwave_js/translations/pt-BR.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "Deseja configurar o {name} com o add-on Z-Wave JS?" + }, + "zeroconf_confirm": { + "description": "Deseja adicionar o servidor Z-Wave JS com o ID inicial {home_id} encontrado em {url} ao Home Assistant?", + "title": "Servidor Z-Wave JS descoberto" } } }, diff --git a/homeassistant/components/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json index bc1d3e2cbd4..14011c78517 100644 --- a/homeassistant/components/zwave_js/translations/ru.json +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS?" + }, + "zeroconf_confirm": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Z-Wave JS \u0441 home ID {home_id}, \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 {url}?", + "title": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440 Z-Wave JS" } } }, diff --git a/homeassistant/components/zwave_js/translations/tr.json b/homeassistant/components/zwave_js/translations/tr.json index efaa87cc48d..a3b5f6fb9f4 100644 --- a/homeassistant/components/zwave_js/translations/tr.json +++ b/homeassistant/components/zwave_js/translations/tr.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "{name} Z-Wave JS eklentisiyle kurmak istiyor musunuz?" + }, + "zeroconf_confirm": { + "description": "{url} adresinde bulunan {home_id} ev kimli\u011fine sahip Z-Wave JS Sunucusunu Home Assistant'a eklemek istiyor musunuz?", + "title": "Ke\u015ffedilen Z-Wave JS Sunucusu" } } }, diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index 664cd6b48d9..2ea728a591e 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -59,6 +59,10 @@ }, "usb_confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u540d\u70ba {name} \u7684 Z-Wave JS \u9644\u52a0\u5143\u4ef6\uff1f" + }, + "zeroconf_confirm": { + "description": "\u662f\u5426\u8981\u65b0\u589e\u65bc {url} \u6240\u627e\u5230\u4e4b ID \u70ba {home_id} \u4e4b Z-Wave JS \u4f3a\u670d\u5668\u81f3 Home Assistant\uff1f", + "title": "\u6240\u767c\u73fe\u7684 Z-Wave JS \u4f3a\u670d\u5668" } } }, diff --git a/homeassistant/components/zwave_me/__init__.py b/homeassistant/components/zwave_me/__init__.py index 6e2ee0fd58c..10312f36dfc 100644 --- a/homeassistant/components/zwave_me/__init__.py +++ b/homeassistant/components/zwave_me/__init__.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) ZWAVE_ME_PLATFORMS = [platform.value for platform in ZWaveMePlatform] -async def async_setup_entry(hass, entry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave-Me from a config entry.""" hass.data.setdefault(DOMAIN, {}) controller = hass.data[DOMAIN][entry.entry_id] = ZWaveMeController(hass, entry) @@ -27,7 +27,7 @@ async def async_setup_entry(hass, entry): raise ConfigEntryNotReady() -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/zwave_me/button.py b/homeassistant/components/zwave_me/button.py index 40105d100d4..69354daad43 100644 --- a/homeassistant/components/zwave_me/button.py +++ b/homeassistant/components/zwave_me/button.py @@ -2,8 +2,10 @@ from typing import Any from homeassistant.components.button import ButtonEntity -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ZWaveMeEntity from .const import DOMAIN, ZWaveMePlatform @@ -11,7 +13,11 @@ from .const import DOMAIN, ZWaveMePlatform DEVICE_NAME = ZWaveMePlatform.BUTTON -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the number platform.""" @callback diff --git a/homeassistant/components/zwave_me/climate.py b/homeassistant/components/zwave_me/climate.py index 140c397ecde..373dbec2836 100644 --- a/homeassistant/components/zwave_me/climate.py +++ b/homeassistant/components/zwave_me/climate.py @@ -4,10 +4,7 @@ from __future__ import annotations from zwave_me_ws import ZWaveMeData from homeassistant.components.climate import ClimateEntity -from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - SUPPORT_TARGET_TEMPERATURE, -) +from homeassistant.components.climate.const import ClimateEntityFeature, HVACMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import HomeAssistant, callback @@ -51,6 +48,10 @@ async def async_setup_entry( class ZWaveMeClimate(ZWaveMeEntity, ClimateEntity): """Representation of a ZWaveMe sensor.""" + _attr_hvac_mode = HVACMode.HEAT + _attr_hvac_modes = [HVACMode.HEAT] + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + def set_temperature(self, **kwargs) -> None: """Set new target temperature.""" if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: @@ -80,21 +81,6 @@ class ZWaveMeClimate(ZWaveMeEntity, ClimateEntity): """Return max temperature for the device.""" return self.device.min - @property - def hvac_modes(self) -> list[str]: - """Return the list of available operation modes.""" - return [HVAC_MODE_HEAT] - - @property - def hvac_mode(self) -> str: - """Return the current mode.""" - return HVAC_MODE_HEAT - - @property - def supported_features(self) -> int: - """Return the supported features.""" - return SUPPORT_TARGET_TEMPERATURE - @property def target_temperature_step(self) -> float: """Return the supported step of target temperature.""" diff --git a/homeassistant/components/zwave_me/const.py b/homeassistant/components/zwave_me/const.py index cbb096c91f3..475d6394d64 100644 --- a/homeassistant/components/zwave_me/const.py +++ b/homeassistant/components/zwave_me/const.py @@ -13,6 +13,7 @@ class ZWaveMePlatform(StrEnum): BUTTON = "toggleButton" CLIMATE = "thermostat" COVER = "motor" + FAN = "fan" LOCK = "doorlock" NUMBER = "switchMultilevel" SWITCH = "switchBinary" @@ -27,6 +28,7 @@ PLATFORMS = [ Platform.BUTTON, Platform.CLIMATE, Platform.COVER, + Platform.FAN, Platform.LIGHT, Platform.LOCK, Platform.NUMBER, diff --git a/homeassistant/components/zwave_me/cover.py b/homeassistant/components/zwave_me/cover.py index 0425a99b568..7857306ef1f 100644 --- a/homeassistant/components/zwave_me/cover.py +++ b/homeassistant/components/zwave_me/cover.py @@ -5,13 +5,13 @@ from typing import Any from homeassistant.components.cover import ( ATTR_POSITION, - SUPPORT_CLOSE, - SUPPORT_OPEN, - SUPPORT_SET_POSITION, CoverEntity, + CoverEntityFeature, ) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ZWaveMeEntity from .const import DOMAIN, ZWaveMePlatform @@ -19,7 +19,11 @@ from .const import DOMAIN, ZWaveMePlatform DEVICE_NAME = ZWaveMePlatform.COVER -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the cover platform.""" @callback @@ -43,6 +47,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class ZWaveMeCover(ZWaveMeEntity, CoverEntity): """Representation of a ZWaveMe Multilevel Cover.""" + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + def close_cover(self, **kwargs): """Close cover.""" self.controller.zwave_api.send_command(self.device.id, "exact?level=0") @@ -64,9 +74,7 @@ class ZWaveMeCover(ZWaveMeEntity, CoverEntity): None is unknown, 0 is closed, 100 is fully open. """ - return self.device.level + if self.device.level == 99: # Scale max value + return 100 - @property - def supported_features(self) -> int: - """Return the supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + return self.device.level diff --git a/homeassistant/components/zwave_me/fan.py b/homeassistant/components/zwave_me/fan.py new file mode 100644 index 00000000000..45d238a6541 --- /dev/null +++ b/homeassistant/components/zwave_me/fan.py @@ -0,0 +1,72 @@ +"""Representation of a fan.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.fan import FanEntity, FanEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ZWaveMeEntity +from .const import DOMAIN, ZWaveMePlatform + +DEVICE_NAME = ZWaveMePlatform.FAN + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the fan platform.""" + + @callback + def add_new_device(new_device): + controller = hass.data[DOMAIN][config_entry.entry_id] + fan = ZWaveMeFan(controller, new_device) + + async_add_entities( + [ + fan, + ] + ) + + config_entry.async_on_unload( + async_dispatcher_connect( + hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device + ) + ) + + +class ZWaveMeFan(ZWaveMeEntity, FanEntity): + """Representation of a ZWaveMe Fan.""" + + _attr_supported_features = FanEntityFeature.SET_SPEED + + @property + def percentage(self) -> int: + """Return the current speed as a percentage.""" + if self.device.level == 99: # Scale max value + return 100 + return self.device.level + + def set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + self.controller.zwave_api.send_command( + self.device.id, f"exact?level={min(percentage, 99)}" + ) + + def turn_off(self, **kwargs: Any) -> None: + """Turn the fan off.""" + self.controller.zwave_api.send_command(self.device.id, "exact?level=0") + + def turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs, + ) -> None: + """Turn on the fan.""" + self.set_percentage(percentage if percentage is not None else 99) diff --git a/homeassistant/components/zwave_me/light.py b/homeassistant/components/zwave_me/light.py index df2a14d3bbd..fd4488b8dbf 100644 --- a/homeassistant/components/zwave_me/light.py +++ b/homeassistant/components/zwave_me/light.py @@ -5,7 +5,7 @@ from typing import Any from zwave_me_ws import ZWaveMeData -from homeassistant.components.light import ATTR_RGB_COLOR, COLOR_MODE_RGB, LightEntity +from homeassistant.components.light import ATTR_RGB_COLOR, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -45,6 +45,9 @@ async def async_setup_entry( class ZWaveMeRGB(ZWaveMeEntity, LightEntity): """Representation of a ZWaveMe light.""" + _attr_supported_color_modes = {ColorMode.RGB} + _attr_color_mode = ColorMode.RGB + def turn_off(self, **kwargs: Any) -> None: """Turn the device on.""" self.controller.zwave_api.send_command(self.device.id, "off") @@ -73,13 +76,3 @@ class ZWaveMeRGB(ZWaveMeEntity, LightEntity): """Return the rgb color value [int, int, int].""" rgb = self.device.color return rgb["r"], rgb["g"], rgb["b"] - - @property - def supported_color_modes(self) -> set: - """Return all color modes.""" - return {COLOR_MODE_RGB} - - @property - def color_mode(self) -> str: - """Return current color mode.""" - return COLOR_MODE_RGB diff --git a/homeassistant/components/zwave_me/manifest.json b/homeassistant/components/zwave_me/manifest.json index a3303bb9de4..f69bbc1eea1 100644 --- a/homeassistant/components/zwave_me/manifest.json +++ b/homeassistant/components/zwave_me/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave.Me", "documentation": "https://www.home-assistant.io/integrations/zwave_me", "iot_class": "local_push", - "requirements": ["zwave_me_ws==0.2.3", "url-normalize==1.4.1"], + "requirements": ["zwave_me_ws==0.2.4", "url-normalize==1.4.1"], "after_dependencies": ["zeroconf"], "zeroconf": [{ "type": "_hap._tcp.local.", "name": "*z.wave-me*" }], "config_flow": true, diff --git a/homeassistant/components/zwave_me/number.py b/homeassistant/components/zwave_me/number.py index b955ade21db..efa7ceb8603 100644 --- a/homeassistant/components/zwave_me/number.py +++ b/homeassistant/components/zwave_me/number.py @@ -1,7 +1,9 @@ """Representation of a switchMultilevel.""" from homeassistant.components.number import NumberEntity -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ZWaveMeEntity from .const import DOMAIN, ZWaveMePlatform @@ -9,7 +11,11 @@ from .const import DOMAIN, ZWaveMePlatform DEVICE_NAME = ZWaveMePlatform.NUMBER -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the number platform.""" @callback @@ -36,6 +42,8 @@ class ZWaveMeNumber(ZWaveMeEntity, NumberEntity): @property def value(self): """Return the unit of measurement.""" + if self.device.level == 99: # Scale max value + return 100 return self.device.level def set_value(self, value: float) -> None: diff --git a/homeassistant/components/zwave_me/sensor.py b/homeassistant/components/zwave_me/sensor.py index 84c4f406495..ec115d60675 100644 --- a/homeassistant/components/zwave_me/sensor.py +++ b/homeassistant/components/zwave_me/sensor.py @@ -1,6 +1,9 @@ """Representation of a sensorMultilevel.""" from __future__ import annotations +from collections.abc import Callable +from dataclasses import dataclass + from zwave_me_ws import ZWaveMeData from homeassistant.components.sensor import ( @@ -11,11 +14,13 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ELECTRIC_CURRENT_AMPERE, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, LIGHT_LUX, + PERCENTAGE, POWER_WATT, - SIGNAL_STRENGTH_DECIBELS, + PRESSURE_KPA, TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback @@ -25,50 +30,83 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ZWaveMeController, ZWaveMeEntity from .const import DOMAIN, ZWaveMePlatform -SENSORS_MAP: dict[str, SensorEntityDescription] = { - "meterElectric_watt": SensorEntityDescription( - key="meterElectric_watt", - device_class=SensorDeviceClass.POWER, - native_unit_of_measurement=POWER_WATT, + +@dataclass +class ZWaveMeSensorEntityDescription(SensorEntityDescription): + """Class describing ZWaveMeSensor sensor entities.""" + + value: Callable = lambda value: value + + +SENSORS_MAP: dict[str, ZWaveMeSensorEntityDescription] = { + "barometer": ZWaveMeSensorEntityDescription( + key="barometer", + device_class=SensorDeviceClass.PRESSURE, + native_unit_of_measurement=PRESSURE_KPA, state_class=SensorStateClass.MEASUREMENT, ), - "meterElectric_kilowatt_hour": SensorEntityDescription( + "co": ZWaveMeSensorEntityDescription( + key="co", + device_class=SensorDeviceClass.CO, + native_unit_of_measurement="ppm", + state_class=SensorStateClass.MEASUREMENT, + ), + "co2": ZWaveMeSensorEntityDescription( + key="co2", + device_class=SensorDeviceClass.CO2, + native_unit_of_measurement="ppm", + state_class=SensorStateClass.MEASUREMENT, + ), + "humidity": ZWaveMeSensorEntityDescription( + key="humidity", + device_class=SensorDeviceClass.HUMIDITY, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), + "luminosity": ZWaveMeSensorEntityDescription( + key="luminosity", + device_class=SensorDeviceClass.ILLUMINANCE, + native_unit_of_measurement=LIGHT_LUX, + state_class=SensorStateClass.MEASUREMENT, + ), + "meterElectric_ampere": ZWaveMeSensorEntityDescription( + key="meterElectric_ampere", + device_class=SensorDeviceClass.CURRENT, + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + state_class=SensorStateClass.MEASUREMENT, + ), + "meterElectric_kilowatt_hour": ZWaveMeSensorEntityDescription( key="meterElectric_kilowatt_hour", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, ), - "meterElectric_voltage": SensorEntityDescription( + "meterElectric_power_factor": ZWaveMeSensorEntityDescription( + key="meterElectric_power_factor", + device_class=SensorDeviceClass.POWER_FACTOR, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + value=lambda value: float(value) * 100, + ), + "meterElectric_voltage": ZWaveMeSensorEntityDescription( key="meterElectric_voltage", device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, state_class=SensorStateClass.MEASUREMENT, ), - "light": SensorEntityDescription( - key="light", - device_class=SensorDeviceClass.ILLUMINANCE, - native_unit_of_measurement=LIGHT_LUX, + "meterElectric_watt": ZWaveMeSensorEntityDescription( + key="meterElectric_watt", + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=POWER_WATT, state_class=SensorStateClass.MEASUREMENT, ), - "noise": SensorEntityDescription( - key="noise", - device_class=SensorDeviceClass.SIGNAL_STRENGTH, - native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, - state_class=SensorStateClass.MEASUREMENT, - ), - "currentTemperature": SensorEntityDescription( - key="currentTemperature", - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=TEMP_CELSIUS, - state_class=SensorStateClass.MEASUREMENT, - ), - "temperature": SensorEntityDescription( + "temperature": ZWaveMeSensorEntityDescription( key="temperature", device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=TEMP_CELSIUS, state_class=SensorStateClass.MEASUREMENT, ), - "generic": SensorEntityDescription( + "generic": ZWaveMeSensorEntityDescription( key="generic", ), } @@ -104,11 +142,13 @@ async def async_setup_entry( class ZWaveMeSensor(ZWaveMeEntity, SensorEntity): """Representation of a ZWaveMe sensor.""" + entity_description: ZWaveMeSensorEntityDescription + def __init__( self, controller: ZWaveMeController, device: ZWaveMeData, - description: SensorEntityDescription, + description: ZWaveMeSensorEntityDescription, ) -> None: """Initialize the device.""" super().__init__(controller=controller, device=device) @@ -117,4 +157,4 @@ class ZWaveMeSensor(ZWaveMeEntity, SensorEntity): @property def native_value(self) -> str: """Return the state of the sensor.""" - return self.device.level + return self.entity_description.value(self.device.level) diff --git a/homeassistant/components/zwave_me/siren.py b/homeassistant/components/zwave_me/siren.py index c528f610132..f59face65d4 100644 --- a/homeassistant/components/zwave_me/siren.py +++ b/homeassistant/components/zwave_me/siren.py @@ -2,8 +2,10 @@ from typing import Any from homeassistant.components.siren import SirenEntity -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ZWaveMeEntity from .const import DOMAIN, ZWaveMePlatform @@ -11,7 +13,11 @@ from .const import DOMAIN, ZWaveMePlatform DEVICE_NAME = ZWaveMePlatform.SIREN -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the siren platform.""" @callback diff --git a/homeassistant/components/zwave_me/switch.py b/homeassistant/components/zwave_me/switch.py index c759809df15..c9a824c5e0d 100644 --- a/homeassistant/components/zwave_me/switch.py +++ b/homeassistant/components/zwave_me/switch.py @@ -7,8 +7,10 @@ from homeassistant.components.switch import ( SwitchEntity, SwitchEntityDescription, ) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ZWaveMeEntity from .const import DOMAIN, ZWaveMePlatform @@ -24,7 +26,11 @@ SWITCH_MAP: dict[str, SwitchEntityDescription] = { } -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the switch platform.""" @callback diff --git a/homeassistant/components/zwave_me/translations/ca.json b/homeassistant/components/zwave_me/translations/ca.json index a382cef5494..abd616b1ed5 100644 --- a/homeassistant/components/zwave_me/translations/ca.json +++ b/homeassistant/components/zwave_me/translations/ca.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "Token d'API", "url": "URL" }, - "description": "Introdueix l'adre\u00e7a IP del servidor Z-Way i el 'token' d'acc\u00e9s Z-Way. Si s'utilitza HTTPS en lloc d'HTTP, l'adre\u00e7a IP es pot prefixar amb wss://. Per obtenir el 'token', v\u00e9s a la interf\u00edcie d'usuari de Z-Way > Men\u00fa > Configuraci\u00f3 > Usuari > Token API. Es recomana crear un nou usuari de Home Assistant i concedir-li acc\u00e9s als dispositius que necessitis controlar des de Home Assistant. Tamb\u00e9 \u00e9s possible utilitzar l'acc\u00e9s remot a trav\u00e9s de find.z-wave.me per connectar amb un Z-Way remot. Introdueix wss://find.z-wave.me al camp d'IP i copia el 'token' d'\u00e0mbit global (inicia sessi\u00f3 a Z-Way mitjan\u00e7ant find.z-wave.me)." + "description": "Introdueix l'adre\u00e7a IP del servidor Z-Way i el 'token' d'acc\u00e9s Z-Way. Per obtenir el 'token', v\u00e9s a la interf\u00edcie d'usuari de Z-Way Smart Home UI > Men\u00fa > Configuraci\u00f3 > Usuaris > Administrador > Token API.\n\nExemple de connexi\u00f3 a Z-Way en una xarxa local:\nURL: {local_url}\nToken: {local_token}\n\nExemple de connexi\u00f3 a Z-Way a trav\u00e9s de l'acc\u00e9s remot find.z-wave.me:\nURL: {find_url}\nToken: {find_token}\n\nExemple de connexi\u00f3 a Z-Way amb una adre\u00e7a IP p\u00fablica:\nURL: {remote_url}\nToken: {local_token}\n\nSi et connectes a trav\u00e9s de find.z-wave.me, has d'utilitzar un 'token' d'abast global (inicia sessi\u00f3 a Z-Way via find.z-wave.me per fer-ho)." } } } diff --git a/homeassistant/components/zwave_me/translations/de.json b/homeassistant/components/zwave_me/translations/de.json index 5afd788a3fb..747ccf0c9c8 100644 --- a/homeassistant/components/zwave_me/translations/de.json +++ b/homeassistant/components/zwave_me/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits eingerichtet", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "no_valid_uuid_set": "Keine g\u00fcltige UUID gesetzt" }, "error": { @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "API-Token", "url": "URL" }, - "description": "Gib die IP-Adresse des Z-Way-Servers und das Z-Way-Zugangs-Token ein. Der IP-Adresse kann wss:// vorangestellt werden, wenn HTTPS anstelle von HTTP verwendet werden soll. Um das Token zu erhalten, gehe auf Z-Way-Benutzeroberfl\u00e4che > Men\u00fc > Einstellungen > Benutzer > API-Token. Es wird empfohlen, einen neuen Benutzer f\u00fcr Home Assistant zu erstellen und den Ger\u00e4ten, die du \u00fcber den Home Assistant steuern m\u00f6chtest, Zugriff zu gew\u00e4hren. Es ist auch m\u00f6glich, den Fernzugriff \u00fcber find.z-wave.me zu nutzen, um ein entferntes Z-Way zu verbinden. Gib wss://find.z-wave.me in das IP-Feld ein und kopiere das Token mit globalem Geltungsbereich (logge dich dazu \u00fcber find.z-wave.me bei Z-Way ein)." + "description": "Gib die IP-Adresse mit Port und Zugangs-Token des Z-Way-Servers ein. Um das Token zu erhalten, gehe zur Z-Way-Benutzeroberfl\u00e4che Smart Home UI > Men\u00fc > Einstellungen > Benutzer > Administrator > API-Token.\n\nBeispiel f\u00fcr die Verbindung zu Z-Way im lokalen Netzwerk:\nURL: {local_url}\nToken: {local_token}\n\nBeispiel f\u00fcr die Verbindung zu Z-Way \u00fcber den Fernzugriff find.z-wave.me:\nURL: {find_url}\nToken: {find_token}\n\nBeispiel f\u00fcr eine Verbindung zu Z-Way mit einer statischen \u00f6ffentlichen IP-Adresse:\nURL: {remote_url}\nToken: {local_token}\n\nWenn du dich \u00fcber find.z-wave.me verbindest, musst du ein Token mit globalem Geltungsbereich verwenden (logge dich dazu \u00fcber find.z-wave.me bei Z-Way ein)." } } } diff --git a/homeassistant/components/zwave_me/translations/en.json b/homeassistant/components/zwave_me/translations/en.json index 81d09d5c350..0ad88764806 100644 --- a/homeassistant/components/zwave_me/translations/en.json +++ b/homeassistant/components/zwave_me/translations/en.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "API Token", "url": "URL" }, - "description": "Input IP address of Z-Way server and Z-Way access token. IP address can be prefixed with wss:// if HTTPS should be used instead of HTTP. To get the token go to the Z-Way user interface > Menu > Settings > User > API token. It is suggested to create a new user for Home Assistant and grant access to devices you need to control from Home Assistant. It is also possible to use remote access via find.z-wave.me to connect a remote Z-Way. Input wss://find.z-wave.me in IP field and copy the token with Global scope (log-in to Z-Way via find.z-wave.me for this)." + "description": "Input IP address with port and access token of Z-Way server. To get the token go to the Z-Way user interface Smart Home UI > Menu > Settings > Users > Administrator > API token.\n\nExample of connecting to Z-Way in the local network:\nURL: {local_url}\nToken: {local_token}\n\nExample of connecting to Z-Way via remote access find.z-wave.me:\nURL: {find_url}\nToken: {find_token}\n\nExample of connecting to Z-Way with a static public IP address:\nURL: {remote_url}\nToken: {local_token}\n\nWhen connecting via find.z-wave.me you need to use a token with a global scope (log-in to Z-Way via find.z-wave.me for this)." } } } diff --git a/homeassistant/components/zwave_me/translations/et.json b/homeassistant/components/zwave_me/translations/et.json index c07b4b2b5f8..1969e86fc4a 100644 --- a/homeassistant/components/zwave_me/translations/et.json +++ b/homeassistant/components/zwave_me/translations/et.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "API v\u00f5ti", "url": "URL" }, - "description": "Sisesta Z-Way serveri IP-aadress ja Z-Way juurdep\u00e4\u00e4suluba. Kui HTTP asemel tuleks kasutada HTTPS-i, v\u00f5ib IP-aadressile lisada eesliite wss://. Tokeni hankimiseks mine Z-Way kasutajaliidesesse > Men\u00fc\u00fc > Seaded > Kasutaja > API tunnus. Soovitatav on luua Home Assistantile uus kasutaja ja anda juurdep\u00e4\u00e4s seadmetele mida pead Home Assistandi abil juhtima. Kaug-Z-Way \u00fchendamiseks on v\u00f5imalik kasutada ka kaugjuurdep\u00e4\u00e4su l\u00e4bi find.z-wave.me. Sisesta IP v\u00e4ljale wss://find.z-wave.me ja kopeeri globaalse ulatusega luba (selleks logi Z-Waysse sisse saidi find.z-wave.me kaudu)." + "description": "Sisesta IP-aadress koos pordi ja Z-Way serveri juurdep\u00e4\u00e4suv\u00f5tmega. Tokeni hankimiseks ava Z-Way kasutajaliides Smart Home UI > Men\u00fc\u00fc > Seaded > Kasutajad > Administraator > API luba. \n\n N\u00e4ide kohalikus v\u00f5rgus Z-Wayga \u00fchenduse loomisest:\n URL: {local_url}\n Token: {local_token} \n\n N\u00e4ide Z-Wayga \u00fchenduse loomisest kaugjuurdep\u00e4\u00e4su kaudu find.z-wave.me:\n URL: {find_url}\n Token: {find_token} \n\n N\u00e4ide Z-Wayga \u00fchenduse loomisest staatilise avaliku IP-aadressiga:\n URL: {remote_url}\n Token: {local_token} \n\n Kui \u00fchendud saidi find.z-wave.me kaudu, pead kasutama globaalse ulatusega luba (selleks logi Z-Waysse sisse saidi find.z-wave.me kaudu)." } } } diff --git a/homeassistant/components/zwave_me/translations/fr.json b/homeassistant/components/zwave_me/translations/fr.json index 1705796cf77..b53964d96ff 100644 --- a/homeassistant/components/zwave_me/translations/fr.json +++ b/homeassistant/components/zwave_me/translations/fr.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Jeton", + "token": "Jeton d'API", "url": "URL" }, - "description": "Entrez l'adresse IP du serveur Z-Way et le jeton d'acc\u00e8s Z-Way. L'adresse IP peut \u00eatre pr\u00e9c\u00e9d\u00e9e de wss:// si HTTPS doit \u00eatre utilis\u00e9 \u00e0 la place de HTTP. Pour obtenir le jeton, acc\u00e9dez \u00e0 l'interface utilisateur Z-Way > Menu > Param\u00e8tres > Utilisateur > Jeton API. Il est sugg\u00e9r\u00e9 de cr\u00e9er un nouvel utilisateur pour Home Assistant et d'accorder l'acc\u00e8s aux appareils que vous devez contr\u00f4ler \u00e0 partir de Home Assistant. Il est \u00e9galement possible d'utiliser l'acc\u00e8s \u00e0 distance via find.z-wave.me pour connecter un Z-Way distant. Entrez wss://find.z-wave.me dans le champ IP et copiez le jeton avec la port\u00e9e globale (connectez-vous \u00e0 Z-Way via find.z-wave.me pour cela)." + "description": "Saisissez l'adresse IP avec le port ainsi que le jeton d'acc\u00e8s au serveur Z-Way. Pour obtenir le jeton, acc\u00e9dez \u00e0 l'interface utilisateur Z-Way Smart Home UI > Menu > Param\u00e8tres > Utilisateurs > Administrateur > jeton d'API.\n\nExemple de connexion \u00e0 Z-Way sur le r\u00e9seau local\u00a0:\nURL\u00a0: {local_url}\nJeton\u00a0: {local_token}\n\nExemple de connexion \u00e0 Z-Way via l'acc\u00e8s \u00e0 distance find.z-wave.me\u00a0:\nURL\u00a0: {find_url}\nJeton\u00a0: {find_token}\n\nExemple de connexion \u00e0 Z-Way avec une adresse IP publique statique\u00a0:\nURL\u00a0: {remote_url}\nJeton\u00a0: {local_token}\n\nLorsque vous vous connectez via find.z-wave.me, vous devez utiliser un jeton avec une port\u00e9e globale (pour cela, connectez-vous \u00e0 Z-Way via find.z-wave.me)." } } } diff --git a/homeassistant/components/zwave_me/translations/he.json b/homeassistant/components/zwave_me/translations/he.json index 5689db627e2..ad8b69eaee6 100644 --- a/homeassistant/components/zwave_me/translations/he.json +++ b/homeassistant/components/zwave_me/translations/he.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df", + "token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df API", "url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8" } } diff --git a/homeassistant/components/zwave_me/translations/hu.json b/homeassistant/components/zwave_me/translations/hu.json index 679604d6cfa..21ed9026feb 100644 --- a/homeassistant/components/zwave_me/translations/hu.json +++ b/homeassistant/components/zwave_me/translations/hu.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "API Token", "url": "URL" }, - "description": "Adja meg a Z-Way szerver IP-c\u00edm\u00e9t \u00e9s a Z-Way hozz\u00e1f\u00e9r\u00e9si tokent. Az IP-c\u00edm el\u00e9 wss:// el\u0151tagot lehet tenni, ha HTTP helyett HTTPS-t kell haszn\u00e1lni. A token megszerz\u00e9s\u00e9hez l\u00e9pjen a Z-Way felhaszn\u00e1l\u00f3i fel\u00fcletre > Men\u00fc > Be\u00e1ll\u00edt\u00e1sok > Felhaszn\u00e1l\u00f3 > API token men\u00fcpontba. Javasoljuk, hogy hozzon l\u00e9tre egy \u00faj felhaszn\u00e1l\u00f3t a Home Assistanthoz, \u00e9s adjon hozz\u00e1f\u00e9r\u00e9st azoknak az eszk\u00f6z\u00f6knek, amelyeket a Home Assistantb\u00f3l kell vez\u00e9relnie. A t\u00e1voli Z-Way csatlakoztat\u00e1s\u00e1hoz a find.z-wave.me oldalon kereszt\u00fcl t\u00e1voli hozz\u00e1f\u00e9r\u00e9st is haszn\u00e1lhat. \u00cdrja be az IP mez\u0151be a wss://find.z-wave.me c\u00edmet, \u00e9s m\u00e1solja be a tokent glob\u00e1lis hat\u00f3k\u00f6rrel (ehhez jelentkezzen be a Z-Way-be a find.z-wave.me oldalon kereszt\u00fcl)." + "description": "Adja meg a Z-Way szerver IP-c\u00edm\u00e9t, portj\u00e1t \u00e9s hozz\u00e1f\u00e9r\u00e9si jel\u00e9t. A token megszerz\u00e9s\u00e9hez n\u00e9zze meg a Z-Way felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9n a Smart Home UI > Menu > Settings > Users > Administrator > API token men\u00fcpontot.\n\nP\u00e9lda a Z-Wayhez val\u00f3 csatlakoz\u00e1sra a helyi h\u00e1l\u00f3zaton:\nURL: {local_url}\nToken: {local_token}\n\nP\u00e9lda a Z-Wayhez t\u00e1voli el\u00e9r\u00e9ssel t\u00f6rt\u00e9n\u0151 csatlakoz\u00e1sra find.z-wave.me:\nURL: {find_url}\nToken: {find_token}\n\nP\u00e9lda a Z-Wayhez val\u00f3 csatlakoz\u00e1sra statikus nyilv\u00e1nos IP-c\u00edmmel:\nURL: {remote_url}\nToken: {local_token}\n\nA find.z-wave.me oldalon kereszt\u00fcl t\u00f6rt\u00e9n\u0151 csatlakoz\u00e1skor glob\u00e1lis hat\u00f3k\u00f6r\u0171 tokent kell haszn\u00e1lnia (ehhez a find.z-wave.me oldalon kereszt\u00fcl kell bejelentkeznie a Z-Way-be)." } } } diff --git a/homeassistant/components/zwave_me/translations/id.json b/homeassistant/components/zwave_me/translations/id.json index da9ddeb6d67..7e934a6c258 100644 --- a/homeassistant/components/zwave_me/translations/id.json +++ b/homeassistant/components/zwave_me/translations/id.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "Token API", "url": "URL" }, - "description": "Masukkan alamat IP server Z-Way dan token akses Z-Way. Alamat IP dapat diawali dengan wss:// jika HTTPS harus digunakan sebagai pengganti HTTP. Untuk mendapatkan token, buka antarmuka pengguna Z-Way > Menu > Pengaturan > Token API > Pengguna. Disarankan untuk membuat pengguna baru untuk Home Assistant dan memberikan akses ke perangkat yang perlu Anda kontrol dari Home Assistant. Dimungkinkan juga untuk menggunakan akses jarak jauh melalui find.z-wave.me untuk menghubungkan Z-Way jarak jauh. Masukkan wss://find.z-wave.me di bidang IP dan salin token dengan cakupan Global (masuk ke Z-Way melalui find.z-wave.me untuk ini)." + "description": "Masukkan alamat IP dengan port dan token akses server Z-Way. Untuk mendapatkan token, buka antarmuka pengguna Z-Way Smart Home UI > Menu > Settings > Users > Administrator > API token.\n\nContoh menghubungkan ke Z-Way di jaringan lokal:\nURL: {local_url}\nToken: {local_token}\n\nContoh menghubungkan ke Z-Way melalui akses jarak jauh find.z-wave.me:\nURL: {find_url}\nToken: {find_token}\n\nContoh menghubungkan ke Z-Way dengan alamat IP publik statis:\nURL: {remote_url}\nToken: {local_token}\n\nSaat terhubung melalui find.z-wave.me Anda perlu menggunakan token dengan cakupan global (masuk ke Z-Way melalui find.z-wave.me untuk hal ini)." } } } diff --git a/homeassistant/components/zwave_me/translations/it.json b/homeassistant/components/zwave_me/translations/it.json index d60ac60d899..048312af73e 100644 --- a/homeassistant/components/zwave_me/translations/it.json +++ b/homeassistant/components/zwave_me/translations/it.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "Token API", "url": "URL" }, - "description": "Digita l'indirizzo IP del server Z-Way e il token di accesso Z-Way. L'indirizzo IP pu\u00f2 essere preceduto da wss:// se si deve utilizzare HTTPS invece di HTTP. Per ottenere il token, vai all'interfaccia utente di Z-Way > Menu > Impostazioni > Utente > Token API. Si consiglia di creare un nuovo utente per Home Assistant e concedere l'accesso ai dispositivi che devi controllare da Home Assistant. \u00c8 anche possibile utilizzare l'accesso remoto tramite find.z-wave.me per connettere uno Z-Way remoto. Inserisci wss://find.z-wave.me nel campo IP e copia il token con ambito globale (accedi a Z-Way tramite find.z-wave.me per questo)." + "description": "Digita l'indirizzo IP con la porta e il token di accesso del server Z-Way. Per ottenere il token, vai all'interfaccia utente di Z-Way Smart Home UI > Menu > Impostazioni > Utenti > Amministratore > API token. \n\nEsempio di connessione a Z-Way nella rete locale:\nURL: {local_url}\nToken: {local_token} \n\nEsempio di connessione a Z-Way tramite accesso remoto find.z-wave.me:\nURL: {find_url}\nToken: {find_token} \n\nEsempio di connessione a Z-Way con un indirizzo IP pubblico statico:\nURL: {remote_url}\nToken: {local_token} \n\nQuando ti connetti tramite find.z-wave.me devi usare un token con un ambito globale (accedi a Z-Way tramite find.z-wave.me per questo)." } } } diff --git a/homeassistant/components/zwave_me/translations/nl.json b/homeassistant/components/zwave_me/translations/nl.json index a2356ed1035..04692559e81 100644 --- a/homeassistant/components/zwave_me/translations/nl.json +++ b/homeassistant/components/zwave_me/translations/nl.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "API-token", "url": "URL" }, - "description": "Voer het IP-adres van de Z-Way server en het Z-Way toegangstoken in. Het IP adres kan voorafgegaan worden door wss:// indien HTTPS gebruikt moet worden in plaats van HTTP. Om het token te verkrijgen gaat u naar de Z-Way gebruikersinterface > Menu > Instellingen > Gebruiker > API token. Het is aanbevolen om een nieuwe gebruiker voor Home Assistant aan te maken en toegang te verlenen aan apparaten die u wilt bedienen vanuit Home Assistant. Het is ook mogelijk om toegang op afstand te gebruiken via find.z-wave.me om een Z-Way op afstand te verbinden. Voer wss://find.z-wave.me in het IP veld in en kopieer het token met Global scope (log hiervoor in op Z-Way via find.z-wave.me)." + "description": "Voer IP adres met poort en toegangstoken van Z-Way server in. Om het token te verkrijgen gaat u naar de Z-Way gebruikersinterface Smart Home UI > Menu > Instellingen > Gebruikers > Beheerder > API token.\n\nVoorbeeld van verbinding met Z-Way in het lokale netwerk:\nURL: {local_url}\nToken: {local_token}\n\nVoorbeeld van verbinding maken met Z-Way via remote access find.z-wave.me:\nURL: {find_url}\nToken: {find_token}\n\nVoorbeeld van een verbinding met Z-Way met een statisch publiek IP-adres:\nURL: {remote_url}\nToken: {local_token}\n\nWanneer je verbinding maakt via find.z-wave.me moet je een token gebruiken met een globale scope (log hiervoor in op Z-Way via find.z-wave.me)." } } } diff --git a/homeassistant/components/zwave_me/translations/no.json b/homeassistant/components/zwave_me/translations/no.json index 8a9a6928e93..266f74fdb61 100644 --- a/homeassistant/components/zwave_me/translations/no.json +++ b/homeassistant/components/zwave_me/translations/no.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "API-token", "url": "URL" }, - "description": "Skriv inn IP-adressen til Z-Way-serveren og Z-Way-tilgangstoken. IP-adressen kan settes foran med wss:// hvis HTTPS skal brukes i stedet for HTTP. For \u00e5 f\u00e5 tokenet, g\u00e5 til Z-Way-brukergrensesnittet > Meny > Innstillinger > Bruker > API-token. Det foresl\u00e5s \u00e5 opprette en ny bruker for Home Assistant og gi tilgang til enheter du m\u00e5 kontrollere fra Home Assistant. Det er ogs\u00e5 mulig \u00e5 bruke ekstern tilgang via find.z-wave.me for \u00e5 koble til en ekstern Z-Way. Skriv inn wss://find.z-wave.me i IP-feltet og kopier token med Global scope (logg inn p\u00e5 Z-Way via find.z-wave.me for dette)." + "description": "Skriv inn IP-adresse med port og tilgangstoken til Z-Way-serveren. For \u00e5 f\u00e5 tokenet, g\u00e5 til Z-Way-brukergrensesnittet Smart Home UI > Meny > Innstillinger > Brukere > Administrator > API-token. \n\n Eksempel p\u00e5 tilkobling til Z-Way i det lokale nettverket:\n URL: {local_url}\n Token: {local_token} \n\n Eksempel p\u00e5 tilkobling til Z-Way via fjerntilgang find.z-wave.me:\n URL: {find_url}\n Token: {find_token} \n\n Eksempel p\u00e5 tilkobling til Z-Way med en statisk offentlig IP-adresse:\n URL: {remote_url}\n Token: {local_token} \n\n N\u00e5r du kobler til via find.z-wave.me m\u00e5 du bruke en token med et globalt omfang (logg inn p\u00e5 Z-Way via find.z-wave.me for dette)." } } } diff --git a/homeassistant/components/zwave_me/translations/pl.json b/homeassistant/components/zwave_me/translations/pl.json index d75251367cd..bc9d43bed0d 100644 --- a/homeassistant/components/zwave_me/translations/pl.json +++ b/homeassistant/components/zwave_me/translations/pl.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "Token API", "url": "URL" }, - "description": "Wprowad\u017a adres IP serwera Z-Way i token dost\u0119pu Z-Way. Adres IP mo\u017ce by\u0107 poprzedzony wss://, je\u015bli zamiast HTTP powinien by\u0107 u\u017cywany HTTPS. Aby uzyska\u0107 token, przejd\u017a do interfejsu u\u017cytkownika Z-Way > Menu > Ustawienia > U\u017cytkownik > Token API. Sugeruje si\u0119 utworzenie nowego u\u017cytkownika Home Assistant i przyznanie dost\u0119pu do urz\u0105dze\u0144, kt\u00f3rymi chcesz sterowa\u0107 z Home Assistant. Mo\u017cliwe jest r\u00f3wnie\u017c u\u017cycie zdalnego dost\u0119pu za po\u015brednictwem find.z-wave.me, aby po\u0142\u0105czy\u0107 si\u0119 ze zdalnym Z-Way. Wpisz wss://find.z-wave.me w polu IP i skopiuj token z zakresem globalnym (w tym celu zaloguj si\u0119 do Z-Way przez find.z-wave.me)." + "description": "Wprowad\u017a adres IP z portem i tokenem dost\u0119pu serwera Z-Way. Aby uzyska\u0107 token, przejd\u017a do interfejsu u\u017cytkownika Z-Way Smart Home UI > Menu > Ustawienia > U\u017cytkownicy > Administrator > Token API. \n\nPrzyk\u0142ad po\u0142\u0105czenia z Z-Way w sieci lokalnej:\nURL: {local_url}\nToken: {local_token} \n\nPrzyk\u0142ad po\u0142\u0105czenia z Z-Way przez zdalny dost\u0119p find.z-wave.me:\nURL: {find_url}\nToken: {find_token} \n\nPrzyk\u0142ad po\u0142\u0105czenia z Z-Way za pomoc\u0105 statycznego publicznego adresu IP:\nURL: {remote_url}\nToken: {local_token} \n\n\u0141\u0105cz\u0105c si\u0119 przez find.z-wave.me, musisz u\u017cy\u0107 tokena o zasi\u0119gu globalnym (w tym celu zaloguj si\u0119 do Z-Way przez find.z-wave.me)." } } } diff --git a/homeassistant/components/zwave_me/translations/pt-BR.json b/homeassistant/components/zwave_me/translations/pt-BR.json index 4a7be43ddb7..23bdcd4ef9a 100644 --- a/homeassistant/components/zwave_me/translations/pt-BR.json +++ b/homeassistant/components/zwave_me/translations/pt-BR.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Token", + "token": "Token da API", "url": "URL" }, - "description": "Insira o endere\u00e7o IP do servidor Z-Way e o token de acesso Z-Way. O endere\u00e7o IP pode ser prefixado com wss:// e o HTTPS deve ser usado em vez de HTTP. Para obter o token, v\u00e1 para a interface do usu\u00e1rio Z-Way > Menu > Configura\u00e7\u00f5es > Usu\u00e1rio > Token de API. Sugere-se criar um novo usu\u00e1rio para o Home Assistant e conceder acesso aos dispositivos que voc\u00ea quer controlar no Home Assistant. Tamb\u00e9m \u00e9 poss\u00edvel usar o acesso remoto via find.z-wave.me para conectar um Z-Way remoto. Insira wss://find.z-wave.me no campo IP e copie o token com escopo Global (fa\u00e7a login no Z-Way via find.z-wave.me para isso)." + "description": "Insira o endere\u00e7o IP com porta e token de acesso do servidor Z-Way. Para obter o token, v\u00e1 para a interface de usu\u00e1rio Z-Way Smart Home UI > Menu > Settings > Users > Administrator > API token. \n\n Exemplo de conex\u00e3o ao Z-Way na rede local:\n URL: {local_url}\n Token: {local_token} \n\n Exemplo de conex\u00e3o ao Z-Way via acesso remoto find.z-wave.me:\n URL: {find_url}\n Token: {find_token} \n\n Exemplo de conex\u00e3o ao Z-Way com um endere\u00e7o IP p\u00fablico est\u00e1tico:\n URL: {remote_url}\n Token: {local_token} \n\n Ao conectar via find.z-wave.me, voc\u00ea precisa usar um token com escopo global (fa\u00e7a login no Z-Way via find.z-wave.me para isso)." } } } diff --git a/homeassistant/components/zwave_me/translations/ru.json b/homeassistant/components/zwave_me/translations/ru.json index d24b2f7327a..f42a7fd20ca 100644 --- a/homeassistant/components/zwave_me/translations/ru.json +++ b/homeassistant/components/zwave_me/translations/ru.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "\u0422\u043e\u043a\u0435\u043d", + "token": "\u0422\u043e\u043a\u0435\u043d API", "url": "URL-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Z-Way \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 Z-Way. \u0415\u0441\u043b\u0438 \u0432\u043c\u0435\u0441\u0442\u043e HTTP \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c HTTPS, IP-\u0430\u0434\u0440\u0435\u0441 \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c 'wss://'. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 Z-Way > \u041c\u0435\u043d\u044e > \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c > \u0422\u043e\u043a\u0435\u043d API. \u0417\u0430\u0442\u0435\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f Home Assistant \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0412\u044b \u0445\u043e\u0442\u0435\u043b\u0438 \u0431\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u0437 Home Assistant. \u0422\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0447\u0435\u0440\u0435\u0437 find.z-wave.me \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e Z-Way. \u0412\u0432\u0435\u0434\u0438\u0442\u0435 wss://find.z-wave.me \u0432 \u043f\u043e\u043b\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0442\u043e\u043a\u0435\u043d \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0439\u0434\u0438\u0442\u0435 \u0432 Z-Way \u0447\u0435\u0440\u0435\u0437 find.z-wave.me)." + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441, \u043f\u043e\u0440\u0442 \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Z-Way. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 Z-Way Smart Home UI > \u041c\u0435\u043d\u044e > \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 > \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 > \u0422\u043e\u043a\u0435\u043d API. \n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Z-Way \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438:\nURL-\u0430\u0434\u0440\u0435\u0441: {local_url}\n\u0422\u043e\u043a\u0435\u043d: {local_token} \n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Z-Way \u0447\u0435\u0440\u0435\u0437 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f find.z-wave.me:\nURL-\u0430\u0434\u0440\u0435\u0441: {find_url}\n\u0422\u043e\u043a\u0435\u043d: {find_token} \n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Z-Way \u0441\u043e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u043c IP-\u0430\u0434\u0440\u0435\u0441\u043e\u043c:\nURL-\u0430\u0434\u0440\u0435\u0441: {remote_url}\n\u0422\u043e\u043a\u0435\u043d: {local_token} \n\n\u041f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 find.z-wave.me \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0441 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c \u0432 Z-Way \u0447\u0435\u0440\u0435\u0437 find.z-wave.me)." } } } diff --git a/homeassistant/components/zwave_me/translations/tr.json b/homeassistant/components/zwave_me/translations/tr.json index 39d25423fab..90edc6bf4ff 100644 --- a/homeassistant/components/zwave_me/translations/tr.json +++ b/homeassistant/components/zwave_me/translations/tr.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "Anahtar", + "token": "API Anahtar\u0131", "url": "URL" }, - "description": "Z-Way sunucusunun ve Z-Way eri\u015fim belirtecinin IP adresini girin. HTTP yerine HTTPS kullan\u0131lmas\u0131 gerekiyorsa, IP adresinin \u00f6n\u00fcne wss:// eklenebilir. Belirteci almak i\u00e7in Z-Way kullan\u0131c\u0131 aray\u00fcz\u00fc > Men\u00fc > Ayarlar > Kullan\u0131c\u0131 > API belirtecine gidin. Home Assistant i\u00e7in yeni bir kullan\u0131c\u0131 olu\u015fturman\u0131z ve Home Assistant'tan kontrol etmeniz gereken cihazlara eri\u015fim izni vermeniz \u00f6nerilir. Uzak bir Z-Way'i ba\u011flamak i\u00e7in find.z-wave.me arac\u0131l\u0131\u011f\u0131yla uzaktan eri\u015fimi kullanmak da m\u00fcmk\u00fcnd\u00fcr. IP alan\u0131na wss://find.z-wave.me yaz\u0131n ve belirteci Global kapsamla kopyalay\u0131n (bunun i\u00e7in find.z-wave.me arac\u0131l\u0131\u011f\u0131yla Z-Way'de oturum a\u00e7\u0131n)." + "description": "Z-Way sunucusunun ba\u011flant\u0131 noktas\u0131 ve eri\u015fim anahtar\u0131 ile IP adresini girin. Simgeyi almak i\u00e7in Z-Way kullan\u0131c\u0131 aray\u00fcz\u00fc Smart Home UI > Men\u00fc > Ayarlar > Kullan\u0131c\u0131lar > Y\u00f6netici > API anahtar\u0131na gidin. \n\n Yerel a\u011fda Z-Way'e ba\u011flanma \u00f6rne\u011fi:\n URL: {local_url}\n Belirte\u00e7: {local_token} \n\n Z-Way'e uzaktan eri\u015fim find.z-wave.me arac\u0131l\u0131\u011f\u0131yla ba\u011flanma \u00f6rne\u011fi:\n URL: {find_url}\n Belirte\u00e7: {find_token} \n\n Statik bir genel IP adresiyle Z-Way'e ba\u011flanma \u00f6rne\u011fi:\n URL: {remote_url}\n Belirte\u00e7: {local_token} \n\n find.z-wave.me arac\u0131l\u0131\u011f\u0131yla ba\u011flan\u0131rken, k\u00fcresel kapsaml\u0131 bir anahtar kullanman\u0131z gerekir (bunun i\u00e7in Z-Way'de find.z-wave.me arac\u0131l\u0131\u011f\u0131yla oturum a\u00e7\u0131n)." } } } diff --git a/homeassistant/components/zwave_me/translations/zh-Hant.json b/homeassistant/components/zwave_me/translations/zh-Hant.json index ee6eb1e9ae7..9c1178b4063 100644 --- a/homeassistant/components/zwave_me/translations/zh-Hant.json +++ b/homeassistant/components/zwave_me/translations/zh-Hant.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "token": "\u6b0a\u6756", + "token": "API \u6b0a\u6756", "url": "\u7db2\u5740" }, - "description": "\u8f38\u5165 Z-Way \u4f3a\u670d\u5668 IP \u4f4d\u5740\u8207 Z-Way \u5b58\u53d6\u6b0a\u6756\u3002\u5047\u5982\u4f7f\u7528 HTTPS \u800c\u975e HTTP\u3001IP \u4f4d\u5740\u524d\u7db4\u53ef\u80fd\u70ba wss://\u3002\u6b32\u53d6\u5f97\u6b0a\u6756\u3001\u8acb\u81f3 Z-Way \u4f7f\u7528\u8005\u4ecb\u9762 > \u9078\u55ae > \u8a2d\u5b9a > \u4f7f\u7528\u8005 > API \u6b0a\u6756\u7372\u5f97\u3002\u5efa\u8b70\u91dd\u5c0d Home Assistant \u65b0\u5275\u4f7f\u7528\u8005\u4e26\u7531 Home Assistant \u63a7\u5236\u53ef\u4ee5\u5b58\u53d6\u7684\u88dd\u7f6e\u3002\u53e6\u5916\u4e5f\u53ef\u4ee5\u900f\u904e find.z-wave.me \u9023\u7dda\u81f3\u9060\u7aef Z-Way \u4e26\u7372\u5f97\u9060\u7aef\u5b58\u53d6\u529f\u80fd\u3002\u65bc IP \u6b04\u4f4d\u8f38\u5165 wss://find.z-wave.me \u4e26\u7531 Global scope\uff08\u900f\u904e find.z-wave.me \u767b\u5165 Z-Way\uff09\u8907\u88fd\u6b0a\u6756\u3002" + "description": "\u8f38\u5165 Z-Way \u4f3a\u670d\u5668 IP \u4f4d\u5740\u901a\u4fe1\u57e0\u8207\u5b58\u53d6\u6b0a\u6756\u3002\u6b32\u53d6\u5f97\u6b0a\u6756\u3001\u8acb\u81f3 Z-Way \u4f7f\u7528\u8005 Smart Home \u4ecb\u9762 > \u9078\u55ae > \u8a2d\u5b9a > \u4f7f\u7528\u8005 > \u7ba1\u7406\u8005 > API \u6b0a\u6756\u7372\u5f97\u3002\n\n\u4ee5\u672c\u5730\u7aef\u9023\u7dda\u81f3 Z-Way \u7bc4\u4f8b\uff1a\nURL\uff1a{local_url}\n\u6b0a\u6756\uff1a{local_token}\n\n\u900f\u904e\u9060\u7aef\u5b58\u53d6 find.z-wave.me \u9023\u7dda\u81f3 Z-Way \u7bc4\u4f8b\uff1a\nURL\uff1a{find_url}\n\u6b0a\u6756\uff1a{find_token}\n\n\u4ee5\u975c\u614b\u516c\u773e IP \u4f4d\u5740\u9023\u7dda\u81f3 Z-Way \u7bc4\u4f8b\uff1a\nURL\uff1a{remote_url}\n\u6b0a\u6756\uff1a{local_token}\n\n\u7576\u900f\u904e find.z-wave.me \u9023\u7dda\u6642\u3001\u5c07\u9700\u8981\u4f7f\u7528\u4e00\u7d44\u5168\u7bc4\u570d\u91d1\u9470\uff08\u900f\u904e find.z-wave.me \u767b\u5165\u81f3 Z-Way \u4ee5\u53d6\u5f97\uff09\u3002" } } } diff --git a/homeassistant/config.py b/homeassistant/config.py index d5f9eb91b62..5c870918231 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -951,8 +951,9 @@ def async_notify_setup_error( message = "The following integrations and platforms could not be set up:\n\n" for name, link in errors.items(): + show_logs = f"[Show logs](/config/logs?filter={name})" part = f"[{name}]({link})" if link else name - message += f" - {part}\n" + message += f" - {part} ({show_logs})\n" message += "\nPlease check your config and [logs](/config/logs)." diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 933cebebcf5..97278f3b2e3 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio from collections import ChainMap -from collections.abc import Awaitable, Callable, Iterable, Mapping +from collections.abc import Callable, Coroutine, Iterable, Mapping from contextvars import ContextVar import dataclasses from enum import Enum @@ -19,7 +19,7 @@ from .components import persistent_notification from .const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, Platform from .core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback from .exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady, HomeAssistantError -from .helpers import device_registry, entity_registry +from .helpers import device_registry, entity_registry, storage from .helpers.event import async_call_later from .helpers.frame import report from .helpers.typing import UNDEFINED, ConfigType, DiscoveryInfoType, UndefinedType @@ -160,7 +160,7 @@ class OperationNotAllowed(ConfigError): """Raised when a config entry operation is not allowed.""" -UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Awaitable[None]] +UpdateListenerType = Callable[[HomeAssistant, "ConfigEntry"], Coroutine[Any, Any, None]] class ConfigEntry: @@ -803,7 +803,7 @@ class ConfigEntries: self._hass_config = hass_config self._entries: dict[str, ConfigEntry] = {} self._domain_index: dict[str, list[str]] = {} - self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) EntityRegistryDisabledHandler(hass).async_setup() @callback @@ -863,10 +863,8 @@ class ConfigEntries: del self._domain_index[entry.domain] self._async_schedule_save() - dev_reg, ent_reg = await asyncio.gather( - self.hass.helpers.device_registry.async_get_registry(), - self.hass.helpers.entity_registry.async_get_registry(), - ) + dev_reg = device_registry.async_get(self.hass) + ent_reg = entity_registry.async_get(self.hass) dev_reg.async_clear_config_entry(entry_id) ent_reg.async_clear_config_entry(entry_id) @@ -911,7 +909,8 @@ class ConfigEntries: async def async_initialize(self) -> None: """Initialize config entry config.""" # Migrating for config entries stored before 0.73 - config = await self.hass.helpers.storage.async_migrator( + config = await storage.async_migrator( + self.hass, self.hass.config.path(PATH_CONFIG), self._store, old_conf_migrate_func=_old_conf_migrator, diff --git a/homeassistant/const.py b/homeassistant/const.py index e8eee10d950..5c7b28e1156 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -6,8 +6,8 @@ from typing import Final from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 -MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "7" +MINOR_VERSION: Final = 5 +PATCH_VERSION: Final = "0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) @@ -61,6 +61,7 @@ MATCH_ALL: Final = "*" # Entity target all constant ENTITY_MATCH_NONE: Final = "none" ENTITY_MATCH_ALL: Final = "all" +ENTITY_MATCH_ANY: Final = "any" # If no name is specified DEVICE_DEFAULT_NAME: Final = "Unnamed Device" @@ -115,6 +116,7 @@ CONF_COMMAND_STATE: Final = "command_state" CONF_COMMAND_STOP: Final = "command_stop" CONF_CONDITION: Final = "condition" CONF_CONDITIONS: Final = "conditions" +CONF_CONTINUE_ON_ERROR: Final = "continue_on_error" CONF_CONTINUE_ON_TIMEOUT: Final = "continue_on_timeout" CONF_COUNT: Final = "count" CONF_COVERS: Final = "covers" @@ -139,12 +141,15 @@ CONF_DOMAIN: Final = "domain" CONF_DOMAINS: Final = "domains" CONF_EFFECT: Final = "effect" CONF_ELEVATION: Final = "elevation" +CONF_ELSE: Final = "else" CONF_EMAIL: Final = "email" +CONF_ENABLED: Final = "enabled" CONF_ENTITIES: Final = "entities" CONF_ENTITY_CATEGORY: Final = "entity_category" CONF_ENTITY_ID: Final = "entity_id" CONF_ENTITY_NAMESPACE: Final = "entity_namespace" CONF_ENTITY_PICTURE_TEMPLATE: Final = "entity_picture_template" +CONF_ERROR: Final = "error" CONF_EVENT: Final = "event" CONF_EVENT_DATA: Final = "event_data" CONF_EVENT_DATA_TEMPLATE: Final = "event_data_template" @@ -153,6 +158,7 @@ CONF_EXTERNAL_URL: Final = "external_url" CONF_FILENAME: Final = "filename" CONF_FILE_PATH: Final = "file_path" CONF_FOR: Final = "for" +CONF_FOR_EACH: Final = "for_each" CONF_FORCE_UPDATE: Final = "force_update" CONF_FRIENDLY_NAME: Final = "friendly_name" CONF_FRIENDLY_NAME_TEMPLATE: Final = "friendly_name_template" @@ -163,6 +169,7 @@ CONF_HS: Final = "hs" CONF_ICON: Final = "icon" CONF_ICON_TEMPLATE: Final = "icon_template" CONF_ID: Final = "id" +CONF_IF: Final = "if" CONF_INCLUDE: Final = "include" CONF_INTERNAL_URL: Final = "internal_url" CONF_IP_ADDRESS: Final = "ip_address" @@ -172,6 +179,7 @@ CONF_LIGHTS: Final = "lights" CONF_LOCATION: Final = "location" CONF_LONGITUDE: Final = "longitude" CONF_MAC: Final = "mac" +CONF_MATCH: Final = "match" CONF_MAXIMUM: Final = "maximum" CONF_MEDIA_DIRS: Final = "media_dirs" CONF_METHOD: Final = "method" @@ -184,6 +192,7 @@ CONF_NAME: Final = "name" CONF_OFFSET: Final = "offset" CONF_OPTIMISTIC: Final = "optimistic" CONF_PACKAGES: Final = "packages" +CONF_PARALLEL: Final = "parallel" CONF_PARAMS: Final = "params" CONF_PASSWORD: Final = "password" CONF_PATH: Final = "path" @@ -224,10 +233,12 @@ CONF_SOURCE: Final = "source" CONF_SSL: Final = "ssl" CONF_STATE: Final = "state" CONF_STATE_TEMPLATE: Final = "state_template" +CONF_STOP: Final = "stop" CONF_STRUCTURE: Final = "structure" CONF_SWITCHES: Final = "switches" CONF_TARGET: Final = "target" CONF_TEMPERATURE_UNIT: Final = "temperature_unit" +CONF_THEN: Final = "then" CONF_TIMEOUT: Final = "timeout" CONF_TIME_ZONE: Final = "time_zone" CONF_TOKEN: Final = "token" @@ -269,9 +280,6 @@ EVENT_SERVICE_REGISTERED: Final = "service_registered" EVENT_SERVICE_REMOVED: Final = "service_removed" EVENT_STATE_CHANGED: Final = "state_changed" EVENT_THEMES_UPDATED: Final = "themes_updated" -EVENT_TIMER_OUT_OF_SYNC: Final = "timer_out_of_sync" -EVENT_TIME_CHANGED: Final = "time_changed" - # #### DEVICE CLASSES #### # DEVICE_CLASS_* below are deprecated as of 2021.12 @@ -315,6 +323,7 @@ STATE_OPEN: Final = "open" STATE_OPENING: Final = "opening" STATE_CLOSED: Final = "closed" STATE_CLOSING: Final = "closing" +STATE_BUFFERING: Final = "buffering" STATE_PLAYING: Final = "playing" STATE_PAUSED: Final = "paused" STATE_IDLE: Final = "idle" @@ -459,6 +468,8 @@ ATTR_DEVICE_CLASS: Final = "device_class" # Temperature attribute ATTR_TEMPERATURE: Final = "temperature" +# Persons attribute +ATTR_PERSONS: Final = "persons" # #### UNITS OF MEASUREMENT #### # Apparent power units @@ -753,11 +764,9 @@ CLOUD_NEVER_EXPOSED_ENTITIES: Final[list[str]] = ["group.all_locks"] # use the EntityCategory enum instead. ENTITY_CATEGORY_CONFIG: Final = "config" ENTITY_CATEGORY_DIAGNOSTIC: Final = "diagnostic" -ENTITY_CATEGORY_SYSTEM: Final = "system" ENTITY_CATEGORIES: Final[list[str]] = [ ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_DIAGNOSTIC, - ENTITY_CATEGORY_SYSTEM, ] # The ID of the Home Assistant Media Player Cast App diff --git a/homeassistant/core.py b/homeassistant/core.py index 733281aa5e6..7245dad7864 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -31,6 +31,7 @@ from typing import ( NamedTuple, Optional, TypeVar, + Union, cast, overload, ) @@ -45,8 +46,6 @@ from .backports.enum import StrEnum from .const import ( ATTR_DOMAIN, ATTR_FRIENDLY_NAME, - ATTR_NOW, - ATTR_SECONDS, ATTR_SERVICE, ATTR_SERVICE_DATA, CONF_UNIT_SYSTEM_IMPERIAL, @@ -60,8 +59,6 @@ from .const import ( EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, - EVENT_TIMER_OUT_OF_SYNC, LENGTH_METERS, MATCH_ALL, MAX_LENGTH_EVENT_EVENT_TYPE, @@ -76,7 +73,7 @@ from .exceptions import ( ServiceNotFound, Unauthorized, ) -from .util import dt as dt_util, location, uuid as uuid_util +from .util import dt as dt_util, location, ulid as ulid_util from .util.async_ import ( fire_coroutine_threadsafe, run_callback_threadsafe, @@ -195,18 +192,15 @@ class HassJob(Generic[_R_co]): def __init__(self, target: Callable[..., _R_co]) -> None: """Create a job object.""" - if asyncio.iscoroutine(target): - raise ValueError("Coroutine not allowed to be passed to HassJob") - self.target = target - self.job_type = _get_callable_job_type(target) + self.job_type = _get_hassjob_callable_job_type(target) def __repr__(self) -> str: """Return the job.""" return f"" -def _get_callable_job_type(target: Callable[..., Any]) -> HassJobType: +def _get_hassjob_callable_job_type(target: Callable[..., Any]) -> HassJobType: """Determine the job type from the callable.""" # Check for partials to properly determine if coroutine function check_target = target @@ -217,6 +211,8 @@ def _get_callable_job_type(target: Callable[..., Any]) -> HassJobType: return HassJobType.Coroutinefunction if is_callback(check_target): return HassJobType.Callback + if asyncio.iscoroutine(check_target): + raise ValueError("Coroutine not allowed to be passed to HassJob") return HassJobType.Executor @@ -348,7 +344,6 @@ class HomeAssistant: self.state = CoreState.running self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE) self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) - _async_create_timer(self) def add_job( self, target: Callable[..., Any] | Coroutine[Any, Any, Any], *args: Any @@ -368,14 +363,14 @@ class HomeAssistant: @overload @callback def async_add_job( - self, target: Callable[..., Awaitable[_R]], *args: Any + self, target: Callable[..., Coroutine[Any, Any, _R]], *args: Any ) -> asyncio.Future[_R] | None: ... @overload @callback def async_add_job( - self, target: Callable[..., Awaitable[_R] | _R], *args: Any + self, target: Callable[..., Coroutine[Any, Any, _R] | _R], *args: Any ) -> asyncio.Future[_R] | None: ... @@ -389,7 +384,7 @@ class HomeAssistant: @callback def async_add_job( self, - target: Callable[..., Awaitable[_R] | _R] | Coroutine[Any, Any, _R], + target: Callable[..., Coroutine[Any, Any, _R] | _R] | Coroutine[Any, Any, _R], *args: Any, ) -> asyncio.Future[_R] | None: """Add a job to be executed by the event loop or by an executor. @@ -408,26 +403,26 @@ class HomeAssistant: if asyncio.iscoroutine(target): return self.async_create_task(target) - target = cast(Callable[..., _R], target) + target = cast(Callable[..., Union[Coroutine[Any, Any, _R], _R]], target) return self.async_add_hass_job(HassJob(target), *args) @overload @callback def async_add_hass_job( - self, hassjob: HassJob[Awaitable[_R]], *args: Any + self, hassjob: HassJob[Coroutine[Any, Any, _R]], *args: Any ) -> asyncio.Future[_R] | None: ... @overload @callback def async_add_hass_job( - self, hassjob: HassJob[Awaitable[_R] | _R], *args: Any + self, hassjob: HassJob[Coroutine[Any, Any, _R] | _R], *args: Any ) -> asyncio.Future[_R] | None: ... @callback def async_add_hass_job( - self, hassjob: HassJob[Awaitable[_R] | _R], *args: Any + self, hassjob: HassJob[Coroutine[Any, Any, _R] | _R], *args: Any ) -> asyncio.Future[_R] | None: """Add a HassJob from within the event loop. @@ -438,10 +433,10 @@ class HomeAssistant: task: asyncio.Future[_R] if hassjob.job_type == HassJobType.Coroutinefunction: task = self.loop.create_task( - cast(Callable[..., Awaitable[_R]], hassjob.target)(*args) + cast(Callable[..., Coroutine[Any, Any, _R]], hassjob.target)(*args) ) elif hassjob.job_type == HassJobType.Callback: - self.loop.call_soon(hassjob.target, *args) + self.loop.call_soon(cast(Callable[..., _R], hassjob.target), *args) return None else: task = self.loop.run_in_executor( @@ -454,7 +449,7 @@ class HomeAssistant: return task - def create_task(self, target: Awaitable[Any]) -> None: + def create_task(self, target: Coroutine[Any, Any, Any]) -> None: """Add task to the executor pool. target: target to call. @@ -462,7 +457,7 @@ class HomeAssistant: self.loop.call_soon_threadsafe(self.async_create_task, target) @callback - def async_create_task(self, target: Awaitable[_R]) -> asyncio.Task[_R]: + def async_create_task(self, target: Coroutine[Any, Any, _R]) -> asyncio.Task[_R]: """Create a task from within the eventloop. This method must be run in the event loop. @@ -502,20 +497,20 @@ class HomeAssistant: @overload @callback def async_run_hass_job( - self, hassjob: HassJob[Awaitable[_R]], *args: Any + self, hassjob: HassJob[Coroutine[Any, Any, _R]], *args: Any ) -> asyncio.Future[_R] | None: ... @overload @callback def async_run_hass_job( - self, hassjob: HassJob[Awaitable[_R] | _R], *args: Any + self, hassjob: HassJob[Coroutine[Any, Any, _R] | _R], *args: Any ) -> asyncio.Future[_R] | None: ... @callback def async_run_hass_job( - self, hassjob: HassJob[Awaitable[_R] | _R], *args: Any + self, hassjob: HassJob[Coroutine[Any, Any, _R] | _R], *args: Any ) -> asyncio.Future[_R] | None: """Run a HassJob from within the event loop. @@ -533,14 +528,14 @@ class HomeAssistant: @overload @callback def async_run_job( - self, target: Callable[..., Awaitable[_R]], *args: Any + self, target: Callable[..., Coroutine[Any, Any, _R]], *args: Any ) -> asyncio.Future[_R] | None: ... @overload @callback def async_run_job( - self, target: Callable[..., Awaitable[_R] | _R], *args: Any + self, target: Callable[..., Coroutine[Any, Any, _R] | _R], *args: Any ) -> asyncio.Future[_R] | None: ... @@ -554,7 +549,7 @@ class HomeAssistant: @callback def async_run_job( self, - target: Callable[..., Awaitable[_R] | _R] | Coroutine[Any, Any, _R], + target: Callable[..., Coroutine[Any, Any, _R] | _R] | Coroutine[Any, Any, _R], *args: Any, ) -> asyncio.Future[_R] | None: """Run a job from within the event loop. @@ -567,7 +562,7 @@ class HomeAssistant: if asyncio.iscoroutine(target): return self.async_create_task(target) - target = cast(Callable[..., _R], target) + target = cast(Callable[..., Union[Coroutine[Any, Any, _R], _R]], target) return self.async_run_hass_job(HassJob(target), *args) def block_till_done(self) -> None: @@ -607,7 +602,6 @@ class HomeAssistant: async def _await_and_log_pending(self, pending: Iterable[Awaitable[Any]]) -> None: """Await and log tasks that take a long time.""" - # pylint: disable=no-self-use wait_time = 0 while pending: _, pending = await asyncio.wait(pending, timeout=BLOCK_LOG_TIMEOUT) @@ -701,7 +695,7 @@ class Context: user_id: str | None = attr.ib(default=None) parent_id: str | None = attr.ib(default=None) - id: str = attr.ib(factory=uuid_util.random_uuid_hex) + id: str = attr.ib(factory=ulid_util.ulid_hex) def as_dict(self) -> dict[str, str | None]: """Return a dictionary representation of the context.""" @@ -843,8 +837,7 @@ class EventBus: event = Event(event_type, event_data, origin, time_fired, context) - if event_type != EVENT_TIME_CHANGED: - _LOGGER.debug("Bus:Handling %s", event) + _LOGGER.debug("Bus:Handling %s", event) if not listeners: return @@ -1911,48 +1904,3 @@ class Config: CORE_STORAGE_VERSION, CORE_STORAGE_KEY, private=True, atomic_writes=True ) await store.async_save(data) - - -def _async_create_timer(hass: HomeAssistant) -> None: - """Create a timer that will start on HOMEASSISTANT_START.""" - handle = None - timer_context = Context() - - def schedule_tick(now: datetime.datetime) -> None: - """Schedule a timer tick when the next second rolls around.""" - nonlocal handle - - slp_seconds = 1 - (now.microsecond / 10**6) - target = monotonic() + slp_seconds - handle = hass.loop.call_later(slp_seconds, fire_time_event, target) - - @callback - def fire_time_event(target: float) -> None: - """Fire next time event.""" - now = dt_util.utcnow() - - hass.bus.async_fire( - EVENT_TIME_CHANGED, {ATTR_NOW: now}, time_fired=now, context=timer_context - ) - - # If we are more than a second late, a tick was missed - if (late := monotonic() - target) > 1: - hass.bus.async_fire( - EVENT_TIMER_OUT_OF_SYNC, - {ATTR_SECONDS: late}, - time_fired=now, - context=timer_context, - ) - - schedule_tick(now) - - @callback - def stop_timer(_: Event) -> None: - """Stop the timer.""" - if handle is not None: - handle.cancel() - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer) - - _LOGGER.info("Timer:starting") - schedule_tick(dt_util.utcnow()) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 998e88000bf..710d97f3c34 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -196,6 +196,7 @@ FLOWS = { "lyric", "mailgun", "mazda", + "meater", "melcloud", "met", "met_eireann", @@ -270,6 +271,7 @@ FLOWS = { "pure_energie", "pvoutput", "pvpc_hourly_pricing", + "qnap_qsw", "rachio", "radio_browser", "rainforest_eagle", @@ -288,6 +290,7 @@ FLOWS = { "rpi_power", "rtsp_to_webrtc", "ruckus_unleashed", + "sabnzbd", "samsungtv", "screenlogic", "season", @@ -295,12 +298,14 @@ FLOWS = { "senseme", "sensibo", "sentry", + "senz", "sharkiq", "shelly", "shopping_list", "sia", "simplisafe", "sleepiq", + "slimproto", "sma", "smappee", "smart_meter_texas", @@ -320,9 +325,11 @@ FLOWS = { "speedtestdotnet", "spider", "spotify", + "sql", "squeezebox", "srp_energy", "starline", + "steam_online", "steamist", "stookalert", "subaru", @@ -338,6 +345,7 @@ FLOWS = { "tailscale", "tankerkoenig", "tasmota", + "tautulli", "tellduslive", "tesla_wall_connector", "tibber", @@ -350,6 +358,7 @@ FLOWS = { "traccar", "tractive", "tradfri", + "trafikverket_ferry", "trafikverket_train", "trafikverket_weatherstation", "transmission", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 3780b7914c4..015d70e2939 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -36,6 +36,7 @@ DHCP: list[dict[str, str | bool]] = [ {'domain': 'flux_led', 'hostname': '[ba][lk]*', 'macaddress': 'B4E842*'}, {'domain': 'flux_led', 'hostname': '[hba][flk]*', 'macaddress': 'F0FE6B*'}, {'domain': 'flux_led', 'hostname': 'lwip*', 'macaddress': '8CCE4E*'}, + {'domain': 'flux_led', 'hostname': 'hf-lpb100-zj*'}, {'domain': 'flux_led', 'hostname': 'zengge_[0-9a-f][0-9a-f]_*'}, {'domain': 'flux_led', 'hostname': 'sta*', 'macaddress': 'C82E47*'}, {'domain': 'fronius', 'macaddress': '0003AC*'}, @@ -49,14 +50,19 @@ DHCP: list[dict[str, str | bool]] = [ {'domain': 'hunterdouglas_powerview', 'hostname': 'hunter*', 'macaddress': '002674*'}, + {'domain': 'insteon', 'macaddress': '000EF3*'}, + {'domain': 'insteon', 'registered_devices': True}, {'domain': 'intellifire', 'hostname': 'zentrios-*'}, {'domain': 'isy994', 'registered_devices': True}, {'domain': 'isy994', 'hostname': 'isy*', 'macaddress': '0021B9*'}, + {'domain': 'isy994', 'hostname': 'polisy*', 'macaddress': '000DB9*'}, {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': '48A2E6*'}, {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': 'B82CA0*'}, {'domain': 'lyric', 'hostname': 'lyric-*', 'macaddress': '00D02D*'}, {'domain': 'motion_blinds', 'registered_devices': True}, {'domain': 'motion_blinds', 'hostname': 'motion_*'}, + {'domain': 'motion_blinds', 'hostname': 'brel_*'}, + {'domain': 'motion_blinds', 'hostname': 'connector_*'}, {'domain': 'myq', 'macaddress': '645299*'}, {'domain': 'nest', 'macaddress': '18B430*'}, {'domain': 'nest', 'macaddress': '641666*'}, @@ -117,21 +123,27 @@ DHCP: list[dict[str, str | bool]] = [ {'domain': 'tolo', 'hostname': 'usr-tcp232-ed2'}, {'domain': 'toon', 'hostname': 'eneco-*', 'macaddress': '74C63B*'}, {'domain': 'tplink', 'registered_devices': True}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '60A4B7*'}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '005F67*'}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '1027F5*'}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '403F8C*'}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'C0C9E3*'}, {'domain': 'tplink', 'hostname': 'ep*', 'macaddress': 'E848B8*'}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'E848B8*'}, - {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '909A4A*'}, + {'domain': 'tplink', 'hostname': 'ep*', 'macaddress': '003192*'}, {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '1C3BF3*'}, {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '50C7BF*'}, {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '68FF7B*'}, {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': '98DAC4*'}, {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': 'B09575*'}, {'domain': 'tplink', 'hostname': 'hs*', 'macaddress': 'C006C3*'}, - {'domain': 'tplink', 'hostname': 'ep*', 'macaddress': '003192*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '1C3BF3*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '50C7BF*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '68FF7B*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '98DAC4*'}, + {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': 'B09575*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '60A4B7*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '005F67*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '1027F5*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'B0A7B9*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '403F8C*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'C0C9E3*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '909A4A*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'E848B8*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '003192*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '1C3BF3*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '50C7BF*'}, @@ -139,11 +151,7 @@ DHCP: list[dict[str, str | bool]] = [ {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '98DAC4*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'B09575*'}, {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': 'C006C3*'}, - {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '1C3BF3*'}, - {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '50C7BF*'}, - {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '68FF7B*'}, - {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': '98DAC4*'}, - {'domain': 'tplink', 'hostname': 'lb*', 'macaddress': 'B09575*'}, + {'domain': 'tplink', 'hostname': 'k[lp]*', 'macaddress': '6C5AB0*'}, {'domain': 'tuya', 'macaddress': '105A17*'}, {'domain': 'tuya', 'macaddress': '10D561*'}, {'domain': 'tuya', 'macaddress': '1869D8*'}, diff --git a/homeassistant/generated/ssdp.py b/homeassistant/generated/ssdp.py index 1c0876cd791..851d9b0fd10 100644 --- a/homeassistant/generated/ssdp.py +++ b/homeassistant/generated/ssdp.py @@ -24,7 +24,8 @@ SSDP = { ], "deconz": [ { - "manufacturer": "Royal Philips Electronics" + "manufacturer": "Royal Philips Electronics", + "manufacturerURL": "http://www.dresden-elektronik.de" } ], "denonavr": [ diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 9c3776155e1..b93d7249211 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -103,7 +103,10 @@ ZEROCONF = { "domain": "devolo_home_control" }, { - "domain": "devolo_home_network" + "domain": "devolo_home_network", + "properties": { + "MT": "*" + } } ], "_easylink._tcp.local.": [ @@ -377,6 +380,11 @@ ZEROCONF = { { "domain": "kodi" } + ], + "_zwave-js-server._tcp.local.": [ + { + "domain": "zwave_js" + } ] } @@ -393,7 +401,26 @@ HOMEKIT = { "EB-*": "ecobee", "Healty Home Coach": "netatmo", "Iota": "abode", - "LIFX": "lifx", + "LIFX A19": "lifx", + "LIFX BR30": "lifx", + "LIFX Beam": "lifx", + "LIFX Candle": "lifx", + "LIFX Clean": "lifx", + "LIFX Color": "lifx", + "LIFX DLCOL": "lifx", + "LIFX DLWW": "lifx", + "LIFX Dlight": "lifx", + "LIFX Downlight": "lifx", + "LIFX Filament": "lifx", + "LIFX GU10": "lifx", + "LIFX Lightstrip": "lifx", + "LIFX Mini": "lifx", + "LIFX Nightvision": "lifx", + "LIFX Pls": "lifx", + "LIFX Plus": "lifx", + "LIFX Tile": "lifx", + "LIFX White": "lifx", + "LIFX Z": "lifx", "MYQ": "myq", "NL29": "nanoleaf", "NL42": "nanoleaf", diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 3f3e526b0c2..660c433d8c7 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -145,7 +145,7 @@ class AreaRegistry: normalized_name = None - if name is not UNDEFINED: + if name is not UNDEFINED and name != old.name: normalized_name = normalize_area_name(name) if normalized_name != old.normalized_name and self.async_get_area_by_name( diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 9017c60c23f..7a73f90539c 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -334,7 +334,13 @@ def sync_entity_lifecycle( ent_reg = entity_registry.async_get(hass) async def _add_entity(change_set: CollectionChangeSet) -> Entity: + def entity_removed() -> None: + """Remove entity from entities if it's removed or not added.""" + if change_set.item_id in entities: + entities.pop(change_set.item_id) + entities[change_set.item_id] = create_entity(change_set.item) + entities[change_set.item_id].async_on_remove(entity_removed) return entities[change_set.item_id] async def _remove_entity(change_set: CollectionChangeSet) -> None: @@ -343,11 +349,16 @@ def sync_entity_lifecycle( ) if ent_to_remove is not None: ent_reg.async_remove(ent_to_remove) - else: + elif change_set.item_id in entities: await entities[change_set.item_id].async_remove(force_remove=True) - entities.pop(change_set.item_id) + # Unconditionally pop the entity from the entity list to avoid racing against + # the entity registry event handled by Entity._async_registry_updated + if change_set.item_id in entities: + entities.pop(change_set.item_id) async def _update_entity(change_set: CollectionChangeSet) -> None: + if change_set.item_id not in entities: + return await entities[change_set.item_id].async_update_config(change_set.item) # type: ignore[attr-defined] _func_map: dict[ diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 71e238938cb..8985b7b721c 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -27,12 +27,16 @@ from homeassistant.const import ( CONF_BELOW, CONF_CONDITION, CONF_DEVICE_ID, + CONF_ENABLED, CONF_ENTITY_ID, CONF_ID, + CONF_MATCH, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WEEKDAY, CONF_ZONE, + ENTITY_MATCH_ALL, + ENTITY_MATCH_ANY, STATE_UNAVAILABLE, STATE_UNKNOWN, SUN_EVENT_SUNRISE, @@ -163,6 +167,18 @@ async def async_from_config( if factory is None: raise HomeAssistantError(f'Invalid condition "{condition}" specified {config}') + # Check if condition is not enabled + if not config.get(CONF_ENABLED, True): + + @trace_condition_function + def disabled_condition( + hass: HomeAssistant, variables: TemplateVarsType = None + ) -> bool: + """Condition not enabled, will always pass.""" + return True + + return disabled_condition + # Check for partials to properly determine if coroutine function check_factory = factory while isinstance(check_factory, ft.partial): @@ -524,6 +540,7 @@ def state_from_config(config: ConfigType) -> ConditionCheckerType: req_states: str | list[str] = config.get(CONF_STATE, []) for_period = config.get("for") attribute = config.get(CONF_ATTRIBUTE) + match = config.get(CONF_MATCH, ENTITY_MATCH_ALL) if not isinstance(req_states, list): req_states = [req_states] @@ -532,10 +549,13 @@ def state_from_config(config: ConfigType) -> ConditionCheckerType: def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" errors = [] + result: bool = match != ENTITY_MATCH_ANY for index, entity_id in enumerate(entity_ids): try: with trace_path(["entity_id", str(index)]), trace_condition(variables): - if not state(hass, entity_id, req_states, for_period, attribute): + if state(hass, entity_id, req_states, for_period, attribute): + result = True + elif match == ENTITY_MATCH_ALL: return False except ConditionError as ex: errors.append( @@ -548,7 +568,7 @@ def state_from_config(config: ConfigType) -> ConditionCheckerType: if errors: raise ConditionErrorContainer("state", errors=errors) - return True + return result return if_state diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index cf2d73715de..e2b21522d42 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -32,7 +32,6 @@ from .network import NoURLAvailableError _LOGGER = logging.getLogger(__name__) DATA_JWT_SECRET = "oauth2_jwt_secret" -DATA_VIEW_REGISTERED = "oauth2_view_reg" DATA_IMPLEMENTATIONS = "oauth2_impl" DATA_PROVIDERS = "oauth2_providers" AUTH_CALLBACK_PATH = "/auth/external/callback" @@ -331,12 +330,6 @@ def async_register_implementation( hass: HomeAssistant, domain: str, implementation: AbstractOAuth2Implementation ) -> None: """Register an OAuth2 flow implementation for an integration.""" - if isinstance(implementation, LocalOAuth2Implementation) and not hass.data.get( - DATA_VIEW_REGISTERED, False - ): - hass.http.register_view(OAuth2AuthorizeCallbackView()) - hass.data[DATA_VIEW_REGISTERED] = True - implementations = hass.data.setdefault(DATA_IMPLEMENTATIONS, {}) implementations.setdefault(domain, {})[implementation.domain] = implementation @@ -401,7 +394,6 @@ class OAuth2AuthorizeCallbackView(http.HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Receive authorization code.""" - # pylint: disable=no-self-use if "code" not in request.query or "state" not in request.query: return web.Response( text=f"Missing code or state parameter in {request.url}" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index fb920c4ef1b..f459d96040b 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -36,19 +36,27 @@ from homeassistant.const import ( CONF_CHOOSE, CONF_CONDITION, CONF_CONDITIONS, + CONF_CONTINUE_ON_ERROR, CONF_CONTINUE_ON_TIMEOUT, CONF_COUNT, CONF_DEFAULT, CONF_DELAY, CONF_DEVICE_ID, CONF_DOMAIN, + CONF_ELSE, + CONF_ENABLED, CONF_ENTITY_ID, CONF_ENTITY_NAMESPACE, + CONF_ERROR, CONF_EVENT, CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE, CONF_FOR, + CONF_FOR_EACH, CONF_ID, + CONF_IF, + CONF_MATCH, + CONF_PARALLEL, CONF_PLATFORM, CONF_REPEAT, CONF_SCAN_INTERVAL, @@ -57,7 +65,9 @@ from homeassistant.const import ( CONF_SERVICE, CONF_SERVICE_TEMPLATE, CONF_STATE, + CONF_STOP, CONF_TARGET, + CONF_THEN, CONF_TIMEOUT, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, @@ -68,6 +78,7 @@ from homeassistant.const import ( CONF_WAIT_TEMPLATE, CONF_WHILE, ENTITY_MATCH_ALL, + ENTITY_MATCH_ANY, ENTITY_MATCH_NONE, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, @@ -962,6 +973,41 @@ def custom_serializer(schema: Any) -> Any: return voluptuous_serialize.UNSUPPORTED +def expand_condition_shorthand(value: Any | None) -> Any: + """Expand boolean condition shorthand notations.""" + + if not isinstance(value, dict) or CONF_CONDITIONS in value: + return value + + for key, schema in ( + ("and", AND_CONDITION_SHORTHAND_SCHEMA), + ("or", OR_CONDITION_SHORTHAND_SCHEMA), + ("not", NOT_CONDITION_SHORTHAND_SCHEMA), + ): + try: + schema(value) + return { + CONF_CONDITION: key, + CONF_CONDITIONS: value[key], + **{k: value[k] for k in value if k != key}, + } + except vol.MultipleInvalid: + pass + + if isinstance(value.get(CONF_CONDITION), list): + try: + CONDITION_SHORTHAND_SCHEMA(value) + return { + CONF_CONDITION: "and", + CONF_CONDITIONS: value[CONF_CONDITION], + **{k: value[k] for k in value if k != CONF_CONDITION}, + } + except vol.MultipleInvalid: + pass + + return value + + # Schemas PLATFORM_SCHEMA = vol.Schema( { @@ -1014,6 +1060,8 @@ def make_entity_service_schema( vol.All( vol.Schema( { + # The frontend stores data here. Don't use in core. + vol.Remove("metadata"): dict, **schema, **ENTITY_SERVICE_FIELDS, }, @@ -1046,7 +1094,11 @@ def script_action(value: Any) -> dict: SCRIPT_SCHEMA = vol.All(ensure_list, [script_action]) -SCRIPT_ACTION_BASE_SCHEMA = {vol.Optional(CONF_ALIAS): string} +SCRIPT_ACTION_BASE_SCHEMA = { + vol.Optional(CONF_ALIAS): string, + vol.Optional(CONF_CONTINUE_ON_ERROR): boolean, + vol.Optional(CONF_ENABLED): boolean, +} EVENT_SCHEMA = vol.Schema( { @@ -1084,7 +1136,10 @@ NUMERIC_STATE_THRESHOLD_SCHEMA = vol.Any( vol.Coerce(float), vol.All(str, entity_domain(["input_number", "number", "sensor"])) ) -CONDITION_BASE_SCHEMA = {vol.Optional(CONF_ALIAS): string} +CONDITION_BASE_SCHEMA = { + vol.Optional(CONF_ALIAS): string, + vol.Optional(CONF_ENABLED): boolean, +} NUMERIC_STATE_CONDITION_SCHEMA = vol.All( vol.Schema( @@ -1105,6 +1160,9 @@ STATE_CONDITION_BASE_SCHEMA = { **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "state", vol.Required(CONF_ENTITY_ID): entity_ids_or_uuids, + vol.Optional(CONF_MATCH, default=ENTITY_MATCH_ALL): vol.All( + vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_ANY) + ), vol.Optional(CONF_ATTRIBUTE): str, vol.Optional(CONF_FOR): positive_time_period, # To support use_trigger_value in automation @@ -1213,6 +1271,17 @@ AND_CONDITION_SCHEMA = vol.Schema( } ) +AND_CONDITION_SHORTHAND_SCHEMA = vol.Schema( + { + **CONDITION_BASE_SCHEMA, + vol.Required("and"): vol.All( + ensure_list, + # pylint: disable=unnecessary-lambda + [lambda value: CONDITION_SCHEMA(value)], + ), + } +) + OR_CONDITION_SCHEMA = vol.Schema( { **CONDITION_BASE_SCHEMA, @@ -1225,6 +1294,17 @@ OR_CONDITION_SCHEMA = vol.Schema( } ) +OR_CONDITION_SHORTHAND_SCHEMA = vol.Schema( + { + **CONDITION_BASE_SCHEMA, + vol.Required("or"): vol.All( + ensure_list, + # pylint: disable=unnecessary-lambda + [lambda value: CONDITION_SCHEMA(value)], + ), + } +) + NOT_CONDITION_SCHEMA = vol.Schema( { **CONDITION_BASE_SCHEMA, @@ -1237,12 +1317,24 @@ NOT_CONDITION_SCHEMA = vol.Schema( } ) +NOT_CONDITION_SHORTHAND_SCHEMA = vol.Schema( + { + **CONDITION_BASE_SCHEMA, + vol.Required("not"): vol.All( + ensure_list, + # pylint: disable=unnecessary-lambda + [lambda value: CONDITION_SCHEMA(value)], + ), + } +) + DEVICE_CONDITION_BASE_SCHEMA = vol.Schema( { **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "device", vol.Required(CONF_DEVICE_ID): str, vol.Required(CONF_DOMAIN): str, + vol.Remove("metadata"): dict, } ) @@ -1257,24 +1349,37 @@ dynamic_template_condition_action = vol.All( }, ) +CONDITION_SHORTHAND_SCHEMA = vol.Schema( + { + **CONDITION_BASE_SCHEMA, + vol.Required(CONF_CONDITION): vol.All( + ensure_list, + # pylint: disable=unnecessary-lambda + [lambda value: CONDITION_SCHEMA(value)], + ), + } +) CONDITION_SCHEMA: vol.Schema = vol.Schema( vol.Any( - key_value_schemas( - CONF_CONDITION, - { - "and": AND_CONDITION_SCHEMA, - "device": DEVICE_CONDITION_SCHEMA, - "not": NOT_CONDITION_SCHEMA, - "numeric_state": NUMERIC_STATE_CONDITION_SCHEMA, - "or": OR_CONDITION_SCHEMA, - "state": STATE_CONDITION_SCHEMA, - "sun": SUN_CONDITION_SCHEMA, - "template": TEMPLATE_CONDITION_SCHEMA, - "time": TIME_CONDITION_SCHEMA, - "trigger": TRIGGER_CONDITION_SCHEMA, - "zone": ZONE_CONDITION_SCHEMA, - }, + vol.All( + expand_condition_shorthand, + key_value_schemas( + CONF_CONDITION, + { + "and": AND_CONDITION_SCHEMA, + "device": DEVICE_CONDITION_SCHEMA, + "not": NOT_CONDITION_SCHEMA, + "numeric_state": NUMERIC_STATE_CONDITION_SCHEMA, + "or": OR_CONDITION_SCHEMA, + "state": STATE_CONDITION_SCHEMA, + "sun": SUN_CONDITION_SCHEMA, + "template": TEMPLATE_CONDITION_SCHEMA, + "time": TIME_CONDITION_SCHEMA, + "trigger": TRIGGER_CONDITION_SCHEMA, + "zone": ZONE_CONDITION_SCHEMA, + }, + ), ), dynamic_template_condition_action, ) @@ -1295,23 +1400,26 @@ dynamic_template_condition_action = vol.All( CONDITION_ACTION_SCHEMA: vol.Schema = vol.Schema( - key_value_schemas( - CONF_CONDITION, - { - "and": AND_CONDITION_SCHEMA, - "device": DEVICE_CONDITION_SCHEMA, - "not": NOT_CONDITION_SCHEMA, - "numeric_state": NUMERIC_STATE_CONDITION_SCHEMA, - "or": OR_CONDITION_SCHEMA, - "state": STATE_CONDITION_SCHEMA, - "sun": SUN_CONDITION_SCHEMA, - "template": TEMPLATE_CONDITION_SCHEMA, - "time": TIME_CONDITION_SCHEMA, - "trigger": TRIGGER_CONDITION_SCHEMA, - "zone": ZONE_CONDITION_SCHEMA, - }, - dynamic_template_condition_action, - "a valid template", + vol.All( + expand_condition_shorthand, + key_value_schemas( + CONF_CONDITION, + { + "and": AND_CONDITION_SCHEMA, + "device": DEVICE_CONDITION_SCHEMA, + "not": NOT_CONDITION_SCHEMA, + "numeric_state": NUMERIC_STATE_CONDITION_SCHEMA, + "or": OR_CONDITION_SCHEMA, + "state": STATE_CONDITION_SCHEMA, + "sun": SUN_CONDITION_SCHEMA, + "template": TEMPLATE_CONDITION_SCHEMA, + "time": TIME_CONDITION_SCHEMA, + "trigger": TRIGGER_CONDITION_SCHEMA, + "zone": ZONE_CONDITION_SCHEMA, + }, + dynamic_template_condition_action, + "a list of conditions or a valid template", + ), ) ) @@ -1320,6 +1428,7 @@ TRIGGER_BASE_SCHEMA = vol.Schema( vol.Required(CONF_PLATFORM): str, vol.Optional(CONF_ID): str, vol.Optional(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, + vol.Optional(CONF_ENABLED): boolean, } ) @@ -1357,6 +1466,7 @@ DEVICE_ACTION_BASE_SCHEMA = vol.Schema( **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_DEVICE_ID): string, vol.Required(CONF_DOMAIN): str, + vol.Remove("metadata"): dict, } ) @@ -1372,6 +1482,9 @@ _SCRIPT_REPEAT_SCHEMA = vol.Schema( vol.Required(CONF_REPEAT): vol.All( { vol.Exclusive(CONF_COUNT, "repeat"): vol.Any(vol.Coerce(int), template), + vol.Exclusive(CONF_FOR_EACH, "repeat"): vol.Any( + dynamic_template, vol.All(list, template_complex) + ), vol.Exclusive(CONF_WHILE, "repeat"): vol.All( ensure_list, [CONDITION_SCHEMA] ), @@ -1380,7 +1493,7 @@ _SCRIPT_REPEAT_SCHEMA = vol.Schema( ), vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, }, - has_at_least_one_key(CONF_COUNT, CONF_WHILE, CONF_UNTIL), + has_at_least_one_key(CONF_COUNT, CONF_FOR_EACH, CONF_WHILE, CONF_UNTIL), ), } ) @@ -1413,6 +1526,15 @@ _SCRIPT_WAIT_FOR_TRIGGER_SCHEMA = vol.Schema( } ) +_SCRIPT_IF_SCHEMA = vol.Schema( + { + **SCRIPT_ACTION_BASE_SCHEMA, + vol.Required(CONF_IF): vol.All(ensure_list, [CONDITION_SCHEMA]), + vol.Required(CONF_THEN): SCRIPT_SCHEMA, + vol.Optional(CONF_ELSE): SCRIPT_SCHEMA, + } +) + _SCRIPT_SET_SCHEMA = vol.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, @@ -1420,6 +1542,39 @@ _SCRIPT_SET_SCHEMA = vol.Schema( } ) +_SCRIPT_STOP_SCHEMA = vol.Schema( + { + **SCRIPT_ACTION_BASE_SCHEMA, + vol.Required(CONF_STOP): vol.Any(None, string), + vol.Optional(CONF_ERROR, default=False): boolean, + } +) + +_SCRIPT_PARALLEL_SEQUENCE = vol.Schema( + { + **SCRIPT_ACTION_BASE_SCHEMA, + vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, + } +) + +_parallel_sequence_action = vol.All( + # Wrap a shorthand sequences in a parallel action + SCRIPT_SCHEMA, + lambda config: { + CONF_SEQUENCE: config, + }, +) + +_SCRIPT_PARALLEL_SCHEMA = vol.Schema( + { + **SCRIPT_ACTION_BASE_SCHEMA, + vol.Required(CONF_PARALLEL): vol.All( + ensure_list, [vol.Any(_SCRIPT_PARALLEL_SEQUENCE, _parallel_sequence_action)] + ), + } +) + + SCRIPT_ACTION_DELAY = "delay" SCRIPT_ACTION_WAIT_TEMPLATE = "wait_template" SCRIPT_ACTION_CHECK_CONDITION = "condition" @@ -1431,6 +1586,9 @@ SCRIPT_ACTION_REPEAT = "repeat" SCRIPT_ACTION_CHOOSE = "choose" SCRIPT_ACTION_WAIT_FOR_TRIGGER = "wait_for_trigger" SCRIPT_ACTION_VARIABLES = "variables" +SCRIPT_ACTION_STOP = "stop" +SCRIPT_ACTION_IF = "if" +SCRIPT_ACTION_PARALLEL = "parallel" def determine_script_action(action: dict[str, Any]) -> str: @@ -1441,7 +1599,7 @@ def determine_script_action(action: dict[str, Any]) -> str: if CONF_WAIT_TEMPLATE in action: return SCRIPT_ACTION_WAIT_TEMPLATE - if CONF_CONDITION in action: + if any(key in action for key in (CONF_CONDITION, "and", "or", "not")): return SCRIPT_ACTION_CHECK_CONDITION if CONF_EVENT in action: @@ -1465,9 +1623,18 @@ def determine_script_action(action: dict[str, Any]) -> str: if CONF_VARIABLES in action: return SCRIPT_ACTION_VARIABLES + if CONF_IF in action: + return SCRIPT_ACTION_IF + if CONF_SERVICE in action or CONF_SERVICE_TEMPLATE in action: return SCRIPT_ACTION_CALL_SERVICE + if CONF_STOP in action: + return SCRIPT_ACTION_STOP + + if CONF_PARALLEL in action: + return SCRIPT_ACTION_PARALLEL + raise ValueError("Unable to determine action") @@ -1483,6 +1650,9 @@ ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { SCRIPT_ACTION_CHOOSE: _SCRIPT_CHOOSE_SCHEMA, SCRIPT_ACTION_WAIT_FOR_TRIGGER: _SCRIPT_WAIT_FOR_TRIGGER_SCHEMA, SCRIPT_ACTION_VARIABLES: _SCRIPT_SET_SCHEMA, + SCRIPT_ACTION_STOP: _SCRIPT_STOP_SCHEMA, + SCRIPT_ACTION_IF: _SCRIPT_IF_SCHEMA, + SCRIPT_ACTION_PARALLEL: _SCRIPT_PARALLEL_SCHEMA, } diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 808207c7f30..2126e048fc5 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -22,7 +22,6 @@ class _BaseFlowManagerView(HomeAssistantView): """Initialize the flow manager index view.""" self._flow_mgr = flow_mgr - # pylint: disable=no-self-use def _prepare_result_json( self, result: data_entry_flow.FlowResult ) -> data_entry_flow.FlowResult: diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 93efd7e69c1..61117fb7d04 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -146,7 +146,7 @@ async def async_load_platform( Warning: Do not await this inside a setup method to avoid a dead lock. Use `hass.async_create_task(async_load_platform(..))` instead. """ - assert hass_config, "You need to pass in the real hass config" + assert hass_config is not None, "You need to pass in the real hass config" setup_success = True diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index f6869787f5b..32163c4498a 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -12,7 +12,7 @@ import logging import math import sys from timeit import default_timer as timer -from typing import Any, Literal, TypedDict, final +from typing import Any, Final, Literal, TypedDict, final import voluptuous as vol @@ -28,7 +28,6 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, - ENTITY_CATEGORIES, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, @@ -65,15 +64,6 @@ SOURCE_PLATFORM_CONFIG = "platform_config" FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1 -def validate_entity_category(value: Any | None) -> EntityCategory: - """Validate entity category configuration.""" - value = vol.In(ENTITY_CATEGORIES)(value) - return EntityCategory(value) - - -ENTITY_CATEGORIES_SCHEMA = validate_entity_category - - @callback @bind_hass def entity_sources(hass: HomeAssistant) -> dict[str, dict[str, str]]: @@ -210,8 +200,8 @@ class EntityCategory(StrEnum): # Diagnostic: An entity exposing some configuration parameter or diagnostics of a device DIAGNOSTIC = "diagnostic" - # System: An entity which is not useful for the user to interact with - SYSTEM = "system" + +ENTITY_CATEGORIES_SCHEMA: Final = vol.Coerce(EntityCategory) class EntityPlatformState(Enum): @@ -237,6 +227,7 @@ class EntityDescription: device_class: str | None = None entity_category: EntityCategory | None = None entity_registry_enabled_default: bool = True + entity_registry_visible_default: bool = True force_update: bool = False icon: str | None = None name: str | None = None @@ -300,6 +291,7 @@ class Entity(ABC): _attr_entity_category: EntityCategory | None _attr_entity_picture: str | None = None _attr_entity_registry_enabled_default: bool + _attr_entity_registry_visible_default: bool _attr_extra_state_attributes: MutableMapping[str, Any] _attr_force_update: bool _attr_icon: str | None @@ -459,6 +451,15 @@ class Entity(ABC): return self.entity_description.entity_registry_enabled_default return True + @property + def entity_registry_visible_default(self) -> bool: + """Return if the entity should be visible when first added to the entity registry.""" + if hasattr(self, "_attr_entity_registry_visible_default"): + return self._attr_entity_registry_visible_default + if hasattr(self, "entity_description"): + return self.entity_description.entity_registry_visible_default + return True + @property def attribution(self) -> str | None: """Return the attribution.""" @@ -755,7 +756,7 @@ class Entity(ABC): @callback def async_on_remove(self, func: CALLBACK_TYPE) -> None: - """Add a function to call when entity removed.""" + """Add a function to call when entity is removed or not added.""" if self._on_remove is None: self._on_remove = [] self._on_remove.append(func) @@ -784,13 +785,23 @@ class Entity(ABC): self.parallel_updates = parallel_updates self._platform_state = EntityPlatformState.ADDED + def _call_on_remove_callbacks(self) -> None: + """Call callbacks registered by async_on_remove.""" + if self._on_remove is None: + return + while self._on_remove: + self._on_remove.pop()() + @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" + + self._platform_state = EntityPlatformState.NOT_ADDED + self._call_on_remove_callbacks() + self.hass = None # type: ignore[assignment] self.platform = None self.parallel_updates = None - self._platform_state = EntityPlatformState.NOT_ADDED async def add_to_platform_finish(self) -> None: """Finish adding an entity to a platform.""" @@ -815,9 +826,7 @@ class Entity(ABC): self._platform_state = EntityPlatformState.REMOVED - if self._on_remove is not None: - while self._on_remove: - self._on_remove.pop()() + self._call_on_remove_callbacks() await self.async_internal_will_remove_from_hass() await self.async_will_remove_from_hass() diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index eaf0ae6d6bb..3d57a9c3dc0 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -42,7 +42,7 @@ from . import ( service, ) from .device_registry import DeviceRegistry -from .entity_registry import EntityRegistry, RegistryEntryDisabler +from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider from .event import async_call_later, async_track_time_interval from .typing import ConfigType, DiscoveryInfoType @@ -507,6 +507,10 @@ class EntityPlatform: if not entity.entity_registry_enabled_default: disabled_by = RegistryEntryDisabler.INTEGRATION + hidden_by: RegistryEntryHider | None = None + if not entity.entity_registry_visible_default: + hidden_by = RegistryEntryHider.INTEGRATION + entry = entity_registry.async_get_or_create( self.domain, self.platform_name, @@ -516,6 +520,7 @@ class EntityPlatform: device_id=device_id, disabled_by=disabled_by, entity_category=entity.entity_category, + hidden_by=hidden_by, known_object_ids=self.entities.keys(), original_device_class=entity.device_class, original_icon=entity.icon, @@ -606,7 +611,7 @@ class EntityPlatform: self.hass.states.async_reserve(entity.entity_id) def remove_entity_cb() -> None: - """Remove entity from entities list.""" + """Remove entity from entities dict.""" self.entities.pop(entity_id) entity.async_on_remove(remove_entity_cb) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index f171f3f7f70..b4dd0820d8c 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -29,6 +29,7 @@ from homeassistant.const import ( MAX_LENGTH_STATE_DOMAIN, MAX_LENGTH_STATE_ENTITY_ID, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import ( Event, @@ -289,7 +290,7 @@ class EntityRegistry: if len(domain) > MAX_LENGTH_STATE_DOMAIN: raise MaxLengthExceeded(domain, "domain", MAX_LENGTH_STATE_DOMAIN) - test_string = preferred_string + test_string = preferred_string[:MAX_LENGTH_STATE_ENTITY_ID] if not known_object_ids: known_object_ids = {} @@ -300,11 +301,9 @@ class EntityRegistry: or not self.hass.states.async_available(test_string) ): tries += 1 - test_string = f"{preferred_string}_{tries}" - - if len(test_string) > MAX_LENGTH_STATE_ENTITY_ID: - raise MaxLengthExceeded( - test_string, "generated_entity_id", MAX_LENGTH_STATE_ENTITY_ID + len_suffix = len(str(tries)) + 1 + test_string = ( + f"{preferred_string[:MAX_LENGTH_STATE_ENTITY_ID-len_suffix]}_{tries}" ) return test_string @@ -484,7 +483,7 @@ class EntityRegistry: ) @callback - def async_update_entity( + def _async_update_entity( self, entity_id: str, *, @@ -505,6 +504,8 @@ class EntityRegistry: original_name: str | None | UndefinedType = UNDEFINED, supported_features: int | UndefinedType = UNDEFINED, unit_of_measurement: str | None | UndefinedType = UNDEFINED, + platform: str | None | UndefinedType = UNDEFINED, + options: Mapping[str, Mapping[str, Any]] | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] @@ -544,6 +545,8 @@ class EntityRegistry: ("original_name", original_name), ("supported_features", supported_features), ("unit_of_measurement", unit_of_measurement), + ("platform", platform), + ("options", options), ): if value is not UNDEFINED and value != getattr(old, attr_name): new_values[attr_name] = value @@ -595,6 +598,87 @@ class EntityRegistry: return new + @callback + def async_update_entity( + self, + entity_id: str, + *, + area_id: str | None | UndefinedType = UNDEFINED, + capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, + config_entry_id: str | None | UndefinedType = UNDEFINED, + device_class: str | None | UndefinedType = UNDEFINED, + device_id: str | None | UndefinedType = UNDEFINED, + disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED, + entity_category: EntityCategory | None | UndefinedType = UNDEFINED, + hidden_by: RegistryEntryHider | None | UndefinedType = UNDEFINED, + icon: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + new_entity_id: str | UndefinedType = UNDEFINED, + new_unique_id: str | UndefinedType = UNDEFINED, + original_device_class: str | None | UndefinedType = UNDEFINED, + original_icon: str | None | UndefinedType = UNDEFINED, + original_name: str | None | UndefinedType = UNDEFINED, + supported_features: int | UndefinedType = UNDEFINED, + unit_of_measurement: str | None | UndefinedType = UNDEFINED, + ) -> RegistryEntry: + """Update properties of an entity.""" + return self._async_update_entity( + entity_id, + area_id=area_id, + capabilities=capabilities, + config_entry_id=config_entry_id, + device_class=device_class, + device_id=device_id, + disabled_by=disabled_by, + entity_category=entity_category, + hidden_by=hidden_by, + icon=icon, + name=name, + new_entity_id=new_entity_id, + new_unique_id=new_unique_id, + original_device_class=original_device_class, + original_icon=original_icon, + original_name=original_name, + supported_features=supported_features, + unit_of_measurement=unit_of_measurement, + ) + + @callback + def async_update_entity_platform( + self, + entity_id: str, + new_platform: str, + *, + new_config_entry_id: str | UndefinedType = UNDEFINED, + new_unique_id: str | UndefinedType = UNDEFINED, + new_device_id: str | None | UndefinedType = UNDEFINED, + ) -> RegistryEntry: + """ + Update entity platform. + + This should only be used when an entity needs to be migrated between + integrations. + """ + if ( + state := self.hass.states.get(entity_id) + ) is not None and state.state != STATE_UNKNOWN: + raise ValueError("Only entities that haven't been loaded can be migrated") + + old = self.entities[entity_id] + if new_config_entry_id == UNDEFINED and old.config_entry_id is not None: + raise ValueError( + f"new_config_entry_id required because {entity_id} is already linked " + "to a config entry" + ) + + return self._async_update_entity( + entity_id, + new_unique_id=new_unique_id, + config_entry_id=new_config_entry_id, + device_id=new_device_id, + platform=new_platform, + ) + @callback def async_update_entity_options( self, entity_id: str, domain: str, options: dict[str, Any] @@ -602,19 +686,7 @@ class EntityRegistry: """Update entity options.""" old = self.entities[entity_id] new_options: Mapping[str, Mapping[str, Any]] = {**old.options, domain: options} - new = self.entities[entity_id] = attr.evolve(old, options=new_options) - - self.async_schedule_save() - - data: dict[str, str | dict[str, Any]] = { - "action": "update", - "entity_id": entity_id, - "changes": {"options": old.options}, - } - - self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) - - return new + return self._async_update_entity(entity_id, options=new_options) async def async_load(self) -> None: """Load the entity registry.""" diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 71b2cf1a585..b85af09cfb9 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -16,10 +16,8 @@ from typing_extensions import Concatenate, ParamSpec from homeassistant.const import ( ATTR_ENTITY_ID, - ATTR_NOW, EVENT_CORE_CONFIG_UPDATE, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, MATCH_ALL, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, @@ -1463,18 +1461,17 @@ def async_track_utc_time_change( local: bool = False, ) -> CALLBACK_TYPE: """Add a listener that will fire if time matches a pattern.""" - job = HassJob(action) # We do not have to wrap the function with time pattern matching logic # if no pattern given if all(val is None for val in (hour, minute, second)): + # Previously this relied on EVENT_TIME_FIRED + # which meant it would not fire right away because + # the caller would always be misaligned with the call + # time vs the fire time by < 1s. To preserve this + # misalignment we use async_track_time_interval here + return async_track_time_interval(hass, action, timedelta(seconds=1)) - @callback - def time_change_listener(event: Event) -> None: - """Fire every time event that comes in.""" - hass.async_run_hass_job(job, cast(datetime, event.data[ATTR_NOW])) - - return hass.bus.async_listen(EVENT_TIME_CHANGED, time_change_listener) - + job = HassJob(action) matching_seconds = dt_util.parse_time_expression(second, 0, 59) matching_minutes = dt_util.parse_time_expression(minute, 0, 59) matching_hours = dt_util.parse_time_expression(hour, 0, 23) @@ -1535,17 +1532,17 @@ track_time_change = threaded_listener_factory(async_track_time_change) def process_state_match( - parameter: None | str | Iterable[str], + parameter: None | str | Iterable[str], invert: bool = False ) -> Callable[[str | None], bool]: """Convert parameter to function that matches input against parameter.""" if parameter is None or parameter == MATCH_ALL: - return lambda _: True + return lambda _: not invert if isinstance(parameter, str) or not hasattr(parameter, "__iter__"): - return lambda state: state == parameter + return lambda state: invert is not (state == parameter) parameter_set = set(parameter) - return lambda state: state in parameter_set + return lambda state: invert is not (state in parameter_set) @callback diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 2baf7cdd713..b81f5f29432 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -109,7 +109,7 @@ def report_integration( integration, found_frame.filename[index:], found_frame.lineno, - found_frame.line.strip(), + (found_frame.line or "?").strip(), ) diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index c1d7487abb6..21d15e4fc73 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio from collections.abc import Awaitable, Callable +from dataclasses import dataclass import logging from typing import Any @@ -12,6 +13,76 @@ from homeassistant.loader import async_get_integration, bind_hass from homeassistant.setup import ATTR_COMPONENT _LOGGER = logging.getLogger(__name__) +DATA_INTEGRATION_PLATFORMS = "integration_platforms" + + +@dataclass(frozen=True) +class IntegrationPlatform: + """An integration platform.""" + + platform_name: str + process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None]] + seen_components: set[str] + + +async def _async_process_single_integration_platform_component( + hass: HomeAssistant, component_name: str, integration_platform: IntegrationPlatform +) -> None: + """Process a single integration platform.""" + if component_name in integration_platform.seen_components: + return + integration_platform.seen_components.add(component_name) + + integration = await async_get_integration(hass, component_name) + platform_name = integration_platform.platform_name + + try: + platform = integration.get_platform(platform_name) + except ImportError as err: + if f"{component_name}.{platform_name}" not in str(err): + _LOGGER.exception( + "Unexpected error importing %s/%s.py", + component_name, + platform_name, + ) + return + + try: + await integration_platform.process_platform(hass, component_name, platform) # type: ignore[misc,operator] # https://github.com/python/mypy/issues/5485 + except Exception: # pylint: disable=broad-except + _LOGGER.exception( + "Error processing platform %s.%s", component_name, platform_name + ) + + +async def async_process_integration_platform_for_component( + hass: HomeAssistant, component_name: str +) -> None: + """Process integration platforms on demand for a component. + + This function will load the integration platforms + for an integration instead of waiting for the EVENT_COMPONENT_LOADED + event to be fired for the integration. + + When the integration will create entities before + it has finished setting up; call this function to ensure + that the integration platforms are loaded before the entities + are created. + """ + if DATA_INTEGRATION_PLATFORMS not in hass.data: + # There are no integration platforms loaded yet + return + integration_platforms: list[IntegrationPlatform] = hass.data[ + DATA_INTEGRATION_PLATFORMS + ] + await asyncio.gather( + *[ + _async_process_single_integration_platform_component( + hass, component_name, integration_platform + ) + for integration_platform in integration_platforms + ] + ) @bind_hass @@ -22,39 +93,30 @@ async def async_process_integration_platforms( process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None]], ) -> None: """Process a specific platform for all current and future loaded integrations.""" + if DATA_INTEGRATION_PLATFORMS not in hass.data: + hass.data[DATA_INTEGRATION_PLATFORMS] = [] - async def _process(component_name: str) -> None: - """Process component being loaded.""" - if "." in component_name: - return + async def _async_component_loaded(event: Event) -> None: + """Handle a new component loaded.""" + comp = event.data[ATTR_COMPONENT] + if "." not in comp: + await async_process_integration_platform_for_component(hass, comp) - integration = await async_get_integration(hass, component_name) + hass.bus.async_listen(EVENT_COMPONENT_LOADED, _async_component_loaded) - try: - platform = integration.get_platform(platform_name) - except ImportError as err: - if f"{component_name}.{platform_name}" not in str(err): - _LOGGER.exception( - "Unexpected error importing %s/%s.py", - component_name, - platform_name, + integration_platforms: list[IntegrationPlatform] = hass.data[ + DATA_INTEGRATION_PLATFORMS + ] + integration_platform = IntegrationPlatform(platform_name, process_platform, set()) + integration_platforms.append(integration_platform) + if top_level_components := ( + comp for comp in hass.config.components if "." not in comp + ): + await asyncio.gather( + *[ + _async_process_single_integration_platform_component( + hass, comp, integration_platform ) - return - - try: - await process_platform(hass, component_name, platform) - except Exception: # pylint: disable=broad-except - _LOGGER.exception( - "Error processing platform %s.%s", component_name, platform_name - ) - - async def async_component_loaded(event: Event) -> None: - """Handle a new component loaded.""" - await _process(event.data[ATTR_COMPONENT]) - - hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_component_loaded) - - tasks = [_process(comp) for comp in hass.config.components] - - if tasks: - await asyncio.gather(*tasks) + for comp in top_level_components + ] + ) diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 5fa10fd6fe8..a6060226d7b 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -236,7 +236,8 @@ def _get_internal_url( scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port ) if ( - not is_loopback(ip_address(ip_url.host)) + ip_url.host + and not is_loopback(ip_address(ip_url.host)) and (not require_current_request or ip_url.host == _get_request_host()) and (not require_standard_port or ip_url.is_default_port()) ): diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index 341ae605025..4073421bc2c 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -371,16 +371,17 @@ def wrapped_entity_config_entry_title( @callback def entity_selector_without_own_entities( handler: SchemaOptionsFlowHandler, - entity_selector_config: dict[str, Any], + entity_selector_config: selector.EntitySelectorConfig, ) -> vol.Schema: """Return an entity selector which excludes own entities.""" entity_registry = er.async_get(handler.hass) entities = er.async_entries_for_config_entry( entity_registry, - handler.config_entry.entry_id, # pylint: disable=protected-access + handler.config_entry.entry_id, ) entity_ids = [ent.entity_id for ent in entities] - return selector.selector( - {"entity": {**entity_selector_config, "exclude_entities": entity_ids}} - ) + final_selector_config = entity_selector_config.copy() + final_selector_config["exclude_entities"] = entity_ids + + return selector.EntitySelector(final_selector_config) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 1ede1d10d89..54ae4f456ab 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -5,6 +5,7 @@ import asyncio from collections.abc import Callable, Sequence from contextlib import asynccontextmanager, suppress from contextvars import ContextVar +from copy import copy from datetime import datetime, timedelta from functools import partial import itertools @@ -27,21 +28,30 @@ from homeassistant.const import ( CONF_CHOOSE, CONF_CONDITION, CONF_CONDITIONS, + CONF_CONTINUE_ON_ERROR, CONF_CONTINUE_ON_TIMEOUT, CONF_COUNT, CONF_DEFAULT, CONF_DELAY, CONF_DEVICE_ID, CONF_DOMAIN, + CONF_ELSE, + CONF_ENABLED, + CONF_ERROR, CONF_EVENT, CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE, + CONF_FOR_EACH, + CONF_IF, CONF_MODE, + CONF_PARALLEL, CONF_REPEAT, CONF_SCENE, CONF_SEQUENCE, CONF_SERVICE, + CONF_STOP, CONF_TARGET, + CONF_THEN, CONF_TIMEOUT, CONF_UNTIL, CONF_VARIABLES, @@ -74,6 +84,7 @@ from .trace import ( trace_id_get, trace_path, trace_path_get, + trace_path_stack_cv, trace_set_result, trace_stack_cv, trace_stack_pop, @@ -191,9 +202,15 @@ async def trace_action(hass, script_run, stop, variables): try: yield trace_element - except _StopScript as ex: + except _AbortScript as ex: trace_element.set_error(ex.__cause__ or ex) raise ex + except _ConditionFail as ex: + # Clear errors which may have been set when evaluating the condition + trace_element.set_error(None) + raise ex + except _StopScript as ex: + raise ex except Exception as ex: trace_element.set_error(ex) raise ex @@ -227,6 +244,7 @@ STATIC_VALIDATION_ACTION_TYPES = ( cv.SCRIPT_ACTION_FIRE_EVENT, cv.SCRIPT_ACTION_ACTIVATE_SCENE, cv.SCRIPT_ACTION_VARIABLES, + cv.SCRIPT_ACTION_STOP, ) @@ -289,13 +307,41 @@ async def async_validate_action_config( hass, choose_conf[CONF_SEQUENCE] ) + elif action_type == cv.SCRIPT_ACTION_IF: + config[CONF_IF] = await condition.async_validate_conditions_config( + hass, config[CONF_IF] + ) + config[CONF_THEN] = await async_validate_actions_config(hass, config[CONF_THEN]) + if CONF_ELSE in config: + config[CONF_ELSE] = await async_validate_actions_config( + hass, config[CONF_ELSE] + ) + + elif action_type == cv.SCRIPT_ACTION_PARALLEL: + for parallel_conf in config[CONF_PARALLEL]: + parallel_conf[CONF_SEQUENCE] = await async_validate_actions_config( + hass, parallel_conf[CONF_SEQUENCE] + ) + else: raise ValueError(f"No validation for {action_type}") return config -class _StopScript(Exception): +class _HaltScript(Exception): + """Throw if script needs to stop executing.""" + + +class _AbortScript(_HaltScript): + """Throw if script needs to abort because of an unexpected error.""" + + +class _ConditionFail(_HaltScript): + """Throw if script needs to stop because a condition evaluated to False.""" + + +class _StopScript(_HaltScript): """Throw if script needs to stop.""" @@ -359,8 +405,18 @@ class _ScriptRun: await self._async_step(log_exceptions=False) else: script_execution_set("finished") - except _StopScript: + except _AbortScript: script_execution_set("aborted") + # Let the _AbortScript bubble up if this is a sub-script + if not self._script.top_level: + raise + except _ConditionFail: + script_execution_set("aborted") + except _StopScript: + script_execution_set("finished") + # Let the _StopScript bubble up if this is a sub-script + if not self._script.top_level: + raise except Exception: script_execution_set("error") raise @@ -370,19 +426,29 @@ class _ScriptRun: self._finish() async def _async_step(self, log_exceptions): + continue_on_error = self._action.get(CONF_CONTINUE_ON_ERROR, False) + with trace_path(str(self._step)): async with trace_action(self._hass, self, self._stop, self._variables): if self._stop.is_set(): return + + action = cv.determine_script_action(self._action) + + if not self._action.get(CONF_ENABLED, True): + self._log( + "Skipped disabled step %s", self._action.get(CONF_ALIAS, action) + ) + trace_set_result(enabled=False) + return + try: - handler = f"_async_{cv.determine_script_action(self._action)}_step" + handler = f"_async_{action}_step" await getattr(self, handler)() - except Exception as ex: - if not isinstance(ex, _StopScript) and ( - self._log_exceptions or log_exceptions - ): - self._log_exception(ex) - raise + except Exception as ex: # pylint: disable=broad-except + self._handle_exception( + ex, continue_on_error, self._log_exceptions or log_exceptions + ) def _finish(self) -> None: self._script._runs.remove(self) # pylint: disable=protected-access @@ -396,6 +462,38 @@ class _ScriptRun: self._stop.set() await self._stopped.wait() + def _handle_exception( + self, exception: Exception, continue_on_error: bool, log_exceptions: bool + ) -> None: + if not isinstance(exception, _HaltScript) and log_exceptions: + self._log_exception(exception) + + if not continue_on_error: + raise exception + + # An explicit request to stop the script has been raised. + if isinstance(exception, _StopScript): + raise exception + + # These are incorrect scripts, and not runtime errors that need to + # be handled and thus cannot be stopped by `continue_on_error`. + if isinstance( + exception, + ( + vol.Invalid, + exceptions.TemplateError, + exceptions.ServiceNotFound, + exceptions.InvalidEntityFormatError, + exceptions.NoEntitySpecifiedError, + exceptions.ConditionError, + ), + ): + raise exception + + # Only Home Assistant errors can be ignored. + if not isinstance(exception, exceptions.HomeAssistantError): + raise exception + def _log_exception(self, exception): action_type = cv.determine_script_action(self._action) @@ -443,7 +541,7 @@ class _ScriptRun: ex, level=logging.ERROR, ) - raise _StopScript from ex + raise _AbortScript from ex async def _async_delay_step(self): """Handle delay.""" @@ -509,7 +607,7 @@ class _ScriptRun: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) trace_set_result(wait=self._variables["wait"], timeout=True) - raise _StopScript from ex + raise _AbortScript from ex finally: for task in tasks: task.cancel() @@ -643,7 +741,7 @@ class _ScriptRun: self._log("Test condition %s: %s", self._script.last_action, check) trace_update_result(result=check) if not check: - raise _StopScript + raise _ConditionFail def _test_conditions(self, conditions, name, condition_path=None): if condition_path is None: @@ -667,17 +765,21 @@ class _ScriptRun: return result @async_trace_path("repeat") - async def _async_repeat_step(self): + async def _async_repeat_step(self): # noqa: C901 """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") repeat = self._action[CONF_REPEAT] saved_repeat_vars = self._variables.get("repeat") - def set_repeat_var(iteration, count=None): + def set_repeat_var( + iteration: int, count: int | None = None, item: Any = None + ) -> None: repeat_vars = {"first": iteration == 1, "index": iteration} if count: repeat_vars["last"] = iteration == count + if item is not None: + repeat_vars["item"] = item self._variables["repeat"] = repeat_vars # pylint: disable=protected-access @@ -700,7 +802,7 @@ class _ScriptRun: ex, level=logging.ERROR, ) - raise _StopScript from ex + raise _AbortScript from ex extra_msg = f" of {count}" for iteration in range(1, count + 1): set_repeat_var(iteration, count) @@ -708,6 +810,35 @@ class _ScriptRun: if self._stop.is_set(): break + elif CONF_FOR_EACH in repeat: + try: + items = template.render_complex(repeat[CONF_FOR_EACH], self._variables) + except (exceptions.TemplateError, ValueError) as ex: + self._log( + "Error rendering %s repeat for each items template: %s", + self._script.name, + ex, + level=logging.ERROR, + ) + raise _AbortScript from ex + + if not isinstance(items, list): + self._log( + "Repeat 'for_each' must be a list of items in %s, got: %s", + self._script.name, + items, + level=logging.ERROR, + ) + raise _AbortScript("Repeat 'for_each' must be a list of items") + + count = len(items) + for iteration, item in enumerate(items, 1): + set_repeat_var(iteration, count, item) + extra_msg = f" of {count} with item: {repr(item)}" + if self._stop.is_set(): + break + await async_run_sequence(iteration, extra_msg) + elif CONF_WHILE in repeat: conditions = [ await self._async_get_condition(config) for config in repeat[CONF_WHILE] @@ -768,6 +899,31 @@ class _ScriptRun: with trace_path(["default"]): await self._async_run_script(choose_data["default"]) + async def _async_if_step(self) -> None: + """If sequence.""" + # pylint: disable=protected-access + if_data = await self._script._async_get_if_data(self._step) + + test_conditions = False + try: + with trace_path("if"): + test_conditions = self._test_conditions( + if_data["if_conditions"], "if", "condition" + ) + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'if' evaluation:\n%s", ex) + + if test_conditions: + trace_set_result(choice="then") + with trace_path("then"): + await self._async_run_script(if_data["if_then"]) + return + + if if_data["if_else"] is not None: + trace_set_result(choice="else") + with trace_path("else"): + await self._async_run_script(if_data["if_else"]) + async def _async_wait_for_trigger_step(self): """Wait for a trigger event.""" if CONF_TIMEOUT in self._action: @@ -820,7 +976,7 @@ class _ScriptRun: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) trace_set_result(wait=self._variables["wait"], timeout=True) - raise _StopScript from ex + raise _AbortScript from ex finally: for task in tasks: task.cancel() @@ -833,6 +989,37 @@ class _ScriptRun: self._hass, self._variables, render_as_defaults=False ) + async def _async_stop_step(self): + """Stop script execution.""" + stop = self._action[CONF_STOP] + error = self._action[CONF_ERROR] + trace_set_result(stop=stop, error=error) + if error: + self._log("Error script sequence: %s", stop) + raise _AbortScript(stop) + self._log("Stop script sequence: %s", stop) + raise _StopScript(stop) + + @async_trace_path("parallel") + async def _async_parallel_step(self) -> None: + """Run a sequence in parallel.""" + # pylint: disable=protected-access + scripts = await self._script._async_get_parallel_scripts(self._step) + + async def async_run_with_trace(idx: int, script: Script) -> None: + """Run a script with a trace path.""" + trace_path_stack_cv.set(copy(trace_path_stack_cv.get())) + with trace_path([str(idx), "sequence"]): + await self._async_run_script(script) + + results = await asyncio.gather( + *(async_run_with_trace(idx, script) for idx, script in enumerate(scripts)), + return_exceptions=True, + ) + for result in results: + if isinstance(result, Exception): + raise result + async def _async_run_script(self, script: Script) -> None: """Execute a script.""" await self._async_run_long_action( @@ -944,6 +1131,12 @@ class _ChooseData(TypedDict): default: Script | None +class _IfData(TypedDict): + if_conditions: list[ConditionCheckerType] + if_then: Script + if_else: Script | None + + class Script: """Representation of a script.""" @@ -955,13 +1148,14 @@ class Script: domain: str, *, # Used in "Running " log message - running_description: str | None = None, change_listener: Callable[..., Any] | None = None, - script_mode: str = DEFAULT_SCRIPT_MODE, - max_runs: int = DEFAULT_MAX, - max_exceeded: str = DEFAULT_MAX_EXCEEDED, - logger: logging.Logger | None = None, + copy_variables: bool = False, log_exceptions: bool = True, + logger: logging.Logger | None = None, + max_exceeded: str = DEFAULT_MAX_EXCEEDED, + max_runs: int = DEFAULT_MAX, + running_description: str | None = None, + script_mode: str = DEFAULT_SCRIPT_MODE, top_level: bool = True, variables: ScriptVariables | None = None, ) -> None: @@ -971,7 +1165,7 @@ class Script: hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, partial(_async_stop_scripts_at_shutdown, hass) ) - self._top_level = top_level + self.top_level = top_level if top_level: all_scripts.append( {"instance": self, "started_before_shutdown": not hass.is_stopping} @@ -1005,6 +1199,8 @@ class Script: self._config_cache: dict[set[tuple], Callable[..., bool]] = {} self._repeat_script: dict[int, Script] = {} self._choose_data: dict[int, _ChooseData] = {} + self._if_data: dict[int, _IfData] = {} + self._parallel_scripts: dict[int, list[Script]] = {} self._referenced_entities: set[str] | None = None self._referenced_devices: set[str] | None = None self._referenced_areas: set[str] | None = None @@ -1012,6 +1208,7 @@ class Script: self._variables_dynamic = template.is_complex(variables) if self._variables_dynamic: template.attach(hass, variables) + self._copy_variables_on_run = copy_variables @property def change_listener(self) -> Callable[..., Any] | None: @@ -1039,11 +1236,18 @@ class Script: self._set_logger(logger) for script in self._repeat_script.values(): script.update_logger(self._logger) + for parallel_scripts in self._parallel_scripts.values(): + for parallel_script in parallel_scripts: + parallel_script.update_logger(self._logger) for choose_data in self._choose_data.values(): for _, script in choose_data["choices"]: script.update_logger(self._logger) if choose_data["default"] is not None: choose_data["default"].update_logger(self._logger) + for if_data in self._if_data.values(): + if_data["if_then"].update_logger(self._logger) + if if_data["if_else"] is not None: + if_data["if_else"].update_logger(self._logger) def _changed(self) -> None: if self._change_listener_job: @@ -1099,6 +1303,15 @@ class Script: if CONF_DEFAULT in step: Script._find_referenced_areas(referenced, step[CONF_DEFAULT]) + elif action == cv.SCRIPT_ACTION_IF: + Script._find_referenced_areas(referenced, step[CONF_THEN]) + if CONF_ELSE in step: + Script._find_referenced_areas(referenced, step[CONF_ELSE]) + + elif action == cv.SCRIPT_ACTION_PARALLEL: + for script in step[CONF_PARALLEL]: + Script._find_referenced_areas(referenced, script[CONF_SEQUENCE]) + @property def referenced_devices(self): """Return a set of referenced devices.""" @@ -1136,6 +1349,17 @@ class Script: if CONF_DEFAULT in step: Script._find_referenced_devices(referenced, step[CONF_DEFAULT]) + elif action == cv.SCRIPT_ACTION_IF: + for cond in step[CONF_IF]: + referenced |= condition.async_extract_devices(cond) + Script._find_referenced_devices(referenced, step[CONF_THEN]) + if CONF_ELSE in step: + Script._find_referenced_devices(referenced, step[CONF_ELSE]) + + elif action == cv.SCRIPT_ACTION_PARALLEL: + for script in step[CONF_PARALLEL]: + Script._find_referenced_devices(referenced, script[CONF_SEQUENCE]) + @property def referenced_entities(self): """Return a set of referenced entities.""" @@ -1174,6 +1398,17 @@ class Script: if CONF_DEFAULT in step: Script._find_referenced_entities(referenced, step[CONF_DEFAULT]) + elif action == cv.SCRIPT_ACTION_IF: + for cond in step[CONF_IF]: + referenced |= condition.async_extract_entities(cond) + Script._find_referenced_entities(referenced, step[CONF_THEN]) + if CONF_ELSE in step: + Script._find_referenced_entities(referenced, step[CONF_ELSE]) + + elif action == cv.SCRIPT_ACTION_PARALLEL: + for script in step[CONF_PARALLEL]: + Script._find_referenced_entities(referenced, script[CONF_SEQUENCE]) + def run( self, variables: _VarsType | None = None, context: Context | None = None ) -> None: @@ -1219,7 +1454,7 @@ class Script: # If this is a top level Script then make a copy of the variables in case they # are read-only, but more importantly, so as not to leak any variables created # during the run back to the caller. - if self._top_level: + if self.top_level: if self.variables: try: variables = self.variables.async_render( @@ -1236,7 +1471,10 @@ class Script: variables["context"] = context else: - variables = cast(dict, run_variables) + if self._copy_variables_on_run: + variables = cast(dict, copy(run_variables)) + else: + variables = cast(dict, run_variables) # Prevent non-allowed recursive calls which will cause deadlocks when we try to # stop (restart) or wait for (queued) our own script run. @@ -1385,6 +1623,89 @@ class Script: self._choose_data[step] = choose_data return choose_data + async def _async_prep_if_data(self, step: int) -> _IfData: + """Prepare data for an if statement.""" + action = self.sequence[step] + step_name = action.get(CONF_ALIAS, f"If at step {step+1}") + + conditions = [ + await self._async_get_condition(config) for config in action[CONF_IF] + ] + + then_script = Script( + self._hass, + action[CONF_THEN], + f"{self.name}: {step_name}", + self.domain, + running_description=self.running_description, + script_mode=SCRIPT_MODE_PARALLEL, + max_runs=self.max_runs, + logger=self._logger, + top_level=False, + ) + then_script.change_listener = partial(self._chain_change_listener, then_script) + + if CONF_ELSE in action: + else_script = Script( + self._hass, + action[CONF_ELSE], + f"{self.name}: {step_name}", + self.domain, + running_description=self.running_description, + script_mode=SCRIPT_MODE_PARALLEL, + max_runs=self.max_runs, + logger=self._logger, + top_level=False, + ) + else_script.change_listener = partial( + self._chain_change_listener, else_script + ) + else: + else_script = None + + return _IfData( + if_conditions=conditions, + if_then=then_script, + if_else=else_script, + ) + + async def _async_get_if_data(self, step: int) -> _IfData: + if not (if_data := self._if_data.get(step)): + if_data = await self._async_prep_if_data(step) + self._if_data[step] = if_data + return if_data + + async def _async_prep_parallel_scripts(self, step: int) -> list[Script]: + action = self.sequence[step] + step_name = action.get(CONF_ALIAS, f"Parallel action at step {step+1}") + parallel_scripts: list[Script] = [] + for idx, parallel_script in enumerate(action[CONF_PARALLEL], start=1): + parallel_name = parallel_script.get(CONF_ALIAS, f"parallel {idx}") + parallel_script = Script( + self._hass, + parallel_script[CONF_SEQUENCE], + f"{self.name}: {step_name}: {parallel_name}", + self.domain, + running_description=self.running_description, + script_mode=SCRIPT_MODE_PARALLEL, + max_runs=self.max_runs, + logger=self._logger, + top_level=False, + copy_variables=True, + ) + parallel_script.change_listener = partial( + self._chain_change_listener, parallel_script + ) + parallel_scripts.append(parallel_script) + + return parallel_scripts + + async def _async_get_parallel_scripts(self, step: int) -> list[Script]: + if not (parallel_scripts := self._parallel_scripts.get(step)): + parallel_scripts = await self._async_prep_parallel_scripts(step) + self._parallel_scripts[step] = parallel_scripts + return parallel_scripts + def _log( self, msg: str, *args: Any, level: int = logging.INFO, **kwargs: Any ) -> None: diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index b4d01ef52e0..eecc66c8332 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -1,11 +1,12 @@ """Selectors for Home Assistant.""" from __future__ import annotations -from collections.abc import Callable -from typing import Any, cast +from collections.abc import Callable, Sequence +from typing import Any, TypedDict, cast import voluptuous as vol +from homeassistant.backports.enum import StrEnum from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT from homeassistant.core import split_entity_id, valid_entity_id from homeassistant.util import decorator @@ -36,11 +37,7 @@ def selector(config: Any) -> Selector: selector_class = _get_selector_class(config) selector_type = list(config)[0] - # Selectors can be empty - if config[selector_type] is None: - return selector_class({selector_type: {}}) - - return selector_class(config) + return selector_class(config[selector_type]) def validate_selector(config: Any) -> dict: @@ -64,9 +61,13 @@ class Selector: config: Any selector_type: str - def __init__(self, config: Any) -> None: + def __init__(self, config: Any = None) -> None: """Instantiate a selector.""" - self.config = self.CONFIG_SCHEMA(config[self.selector_type]) + # Selectors can be empty + if config is None: + config = {} + + self.config = self.CONFIG_SCHEMA(config) def serialize(self) -> Any: """Serialize Selector for voluptuous_serialize.""" @@ -84,6 +85,15 @@ SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema( } ) + +class SingleEntitySelectorConfig(TypedDict, total=False): + """Class to represent a single entity selector config.""" + + integration: str + domain: str + device_class: str + + SINGLE_DEVICE_SELECTOR_CONFIG_SCHEMA = vol.Schema( { # Integration linked to it with a config entry @@ -98,6 +108,19 @@ SINGLE_DEVICE_SELECTOR_CONFIG_SCHEMA = vol.Schema( ) +class SingleDeviceSelectorConfig(TypedDict, total=False): + """Class to represent a single device selector config.""" + + integration: str + manufacturer: str + model: str + entity: SingleEntitySelectorConfig + + +class ActionSelectorConfig(TypedDict): + """Class to represent an action selector config.""" + + @SELECTORS.register("action") class ActionSelector(Selector): """Selector of an action sequence (script syntax).""" @@ -106,11 +129,22 @@ class ActionSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: ActionSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> Any: """Validate the passed selection.""" return data +class AddonSelectorConfig(TypedDict, total=False): + """Class to represent an addon selector config.""" + + name: str + slug: str + + @SELECTORS.register("addon") class AddonSelector(Selector): """Selector of a add-on.""" @@ -124,12 +158,24 @@ class AddonSelector(Selector): } ) + def __init__(self, config: AddonSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str: """Validate the passed selection.""" addon: str = vol.Schema(str)(data) return addon +class AreaSelectorConfig(TypedDict, total=False): + """Class to represent an area selector config.""" + + entity: SingleEntitySelectorConfig + device: SingleDeviceSelectorConfig + multiple: bool + + @SELECTORS.register("area") class AreaSelector(Selector): """Selector of a single or list of areas.""" @@ -144,6 +190,10 @@ class AreaSelector(Selector): } ) + def __init__(self, config: AreaSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: @@ -154,6 +204,12 @@ class AreaSelector(Selector): return [vol.Schema(str)(val) for val in data] +class AttributeSelectorConfig(TypedDict): + """Class to represent an attribute selector config.""" + + entity_id: str + + @SELECTORS.register("attribute") class AttributeSelector(Selector): """Selector for an entity attribute.""" @@ -162,12 +218,20 @@ class AttributeSelector(Selector): CONFIG_SCHEMA = vol.Schema({vol.Required("entity_id"): cv.entity_id}) + def __init__(self, config: AttributeSelectorConfig) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str: """Validate the passed selection.""" attribute: str = vol.Schema(str)(data) return attribute +class BooleanSelectorConfig(TypedDict): + """Class to represent a boolean selector config.""" + + @SELECTORS.register("boolean") class BooleanSelector(Selector): """Selector of a boolean value.""" @@ -176,12 +240,20 @@ class BooleanSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: BooleanSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> bool: """Validate the passed selection.""" value: bool = vol.Coerce(bool)(data) return value +class ColorRGBSelectorConfig(TypedDict): + """Class to represent a color RGB selector config.""" + + @SELECTORS.register("color_rgb") class ColorRGBSelector(Selector): """Selector of an RGB color value.""" @@ -190,12 +262,23 @@ class ColorRGBSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: ColorRGBSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> list[int]: """Validate the passed selection.""" value: list[int] = vol.All(list, vol.ExactSequence((cv.byte,) * 3))(data) return value +class ColorTempSelectorConfig(TypedDict, total=False): + """Class to represent a color temp selector config.""" + + max_mireds: int + min_mireds: int + + @SELECTORS.register("color_temp") class ColorTempSelector(Selector): """Selector of an color temperature.""" @@ -209,6 +292,10 @@ class ColorTempSelector(Selector): } ) + def __init__(self, config: ColorTempSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> int: """Validate the passed selection.""" value: int = vol.All( @@ -221,6 +308,10 @@ class ColorTempSelector(Selector): return value +class DateSelectorConfig(TypedDict): + """Class to represent a date selector config.""" + + @SELECTORS.register("date") class DateSelector(Selector): """Selector of a date.""" @@ -229,12 +320,20 @@ class DateSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: DateSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> Any: """Validate the passed selection.""" cv.date(data) return data +class DateTimeSelectorConfig(TypedDict): + """Class to represent a date time selector config.""" + + @SELECTORS.register("datetime") class DateTimeSelector(Selector): """Selector of a datetime.""" @@ -243,12 +342,26 @@ class DateTimeSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: DateTimeSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> Any: """Validate the passed selection.""" cv.datetime(data) return data +class DeviceSelectorConfig(TypedDict, total=False): + """Class to represent a device selector config.""" + + integration: str + manufacturer: str + model: str + entity: SingleEntitySelectorConfig + multiple: bool + + @SELECTORS.register("device") class DeviceSelector(Selector): """Selector of a single or list of devices.""" @@ -259,6 +372,10 @@ class DeviceSelector(Selector): {vol.Optional("multiple", default=False): cv.boolean} ) + def __init__(self, config: DeviceSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: @@ -269,6 +386,12 @@ class DeviceSelector(Selector): return [vol.Schema(str)(val) for val in data] +class DurationSelectorConfig(TypedDict, total=False): + """Class to represent a duration selector config.""" + + enable_day: bool + + @SELECTORS.register("duration") class DurationSelector(Selector): """Selector for a duration.""" @@ -283,12 +406,24 @@ class DurationSelector(Selector): } ) + def __init__(self, config: DurationSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> dict[str, float]: """Validate the passed selection.""" cv.time_period_dict(data) return cast(dict[str, float], data) +class EntitySelectorConfig(SingleEntitySelectorConfig, total=False): + """Class to represent an entity selector config.""" + + exclude_entities: list[str] + include_entities: list[str] + multiple: bool + + @SELECTORS.register("entity") class EntitySelector(Selector): """Selector of a single or list of entities.""" @@ -303,6 +438,10 @@ class EntitySelector(Selector): } ) + def __init__(self, config: EntitySelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" @@ -333,6 +472,12 @@ class EntitySelector(Selector): return cast(list, vol.Schema([validate])(data)) # Output is a list +class IconSelectorConfig(TypedDict, total=False): + """Class to represent an icon selector config.""" + + placeholder: str + + @SELECTORS.register("icon") class IconSelector(Selector): """Selector for an icon.""" @@ -344,12 +489,23 @@ class IconSelector(Selector): # Frontend also has a fallbackPath option, this is not used by core ) + def __init__(self, config: IconSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str: """Validate the passed selection.""" icon: str = vol.Schema(str)(data) return icon +class LocationSelectorConfig(TypedDict, total=False): + """Class to represent a location selector config.""" + + radius: bool + icon: str + + @SELECTORS.register("location") class LocationSelector(Selector): """Selector for a location.""" @@ -367,12 +523,20 @@ class LocationSelector(Selector): } ) + def __init__(self, config: LocationSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> dict[str, float]: """Validate the passed selection.""" location: dict[str, float] = self.DATA_SCHEMA(data) return location +class MediaSelectorConfig(TypedDict): + """Class to represent a media selector config.""" + + @SELECTORS.register("media") class MediaSelector(Selector): """Selector for media.""" @@ -392,12 +556,33 @@ class MediaSelector(Selector): } ) + def __init__(self, config: MediaSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> dict[str, float]: """Validate the passed selection.""" media: dict[str, float] = self.DATA_SCHEMA(data) return media +class NumberSelectorConfig(TypedDict, total=False): + """Class to represent a number selector config.""" + + min: float + max: float + step: float + unit_of_measurement: str + mode: NumberSelectorMode + + +class NumberSelectorMode(StrEnum): + """Possible modes for a number selector.""" + + BOX = "box" + SLIDER = "slider" + + def has_min_max_if_slider(data: Any) -> Any: """Validate configuration.""" if data["mode"] == "box": @@ -426,12 +611,18 @@ class NumberSelector(Selector): vol.Coerce(float), vol.Range(min=1e-3) ), vol.Optional(CONF_UNIT_OF_MEASUREMENT): str, - vol.Optional(CONF_MODE, default="slider"): vol.In(["box", "slider"]), + vol.Optional(CONF_MODE, default=NumberSelectorMode.SLIDER): vol.Coerce( + NumberSelectorMode + ), } ), has_min_max_if_slider, ) + def __init__(self, config: NumberSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> float: """Validate the passed selection.""" value: float = vol.Coerce(float)(data) @@ -445,6 +636,10 @@ class NumberSelector(Selector): return value +class ObjectSelectorConfig(TypedDict): + """Class to represent an object selector config.""" + + @SELECTORS.register("object") class ObjectSelector(Selector): """Selector for an arbitrary object.""" @@ -453,6 +648,10 @@ class ObjectSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: ObjectSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> Any: """Validate the passed selection.""" return data @@ -469,9 +668,32 @@ select_option = vol.All( ) +class SelectOptionDict(TypedDict): + """Class to represent a select option dict.""" + + value: str + label: str + + +class SelectSelectorMode(StrEnum): + """Possible modes for a number selector.""" + + LIST = "list" + DROPDOWN = "dropdown" + + +class SelectSelectorConfig(TypedDict, total=False): + """Class to represent a select selector config.""" + + options: Sequence[SelectOptionDict] | Sequence[str] # required + multiple: bool + custom_value: bool + mode: SelectSelectorMode + + @SELECTORS.register("select") class SelectSelector(Selector): - """Selector for an single or multi-choice input select.""" + """Selector for an single-choice input select.""" selector_type = "select" @@ -480,10 +702,14 @@ class SelectSelector(Selector): vol.Required("options"): vol.All(vol.Any([str], [select_option])), vol.Optional("multiple", default=False): cv.boolean, vol.Optional("custom_value", default=False): cv.boolean, - vol.Optional("mode"): vol.In(("list", "dropdown")), + vol.Optional("mode"): vol.Coerce(SelectSelectorMode), } ) + def __init__(self, config: SelectSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> Any: """Validate the passed selection.""" options = [] @@ -504,41 +730,11 @@ class SelectSelector(Selector): return [parent_schema(vol.Schema(str)(val)) for val in data] -@SELECTORS.register("text") -class StringSelector(Selector): - """Selector for a multi-line text string.""" +class TargetSelectorConfig(TypedDict, total=False): + """Class to represent a target selector config.""" - selector_type = "text" - - STRING_TYPES = [ - "number", - "text", - "search", - "tel", - "url", - "email", - "password", - "date", - "month", - "week", - "time", - "datetime-local", - "color", - ] - CONFIG_SCHEMA = vol.Schema( - { - vol.Optional("multiline", default=False): bool, - vol.Optional("suffix"): str, - # The "type" controls the input field in the browser, the resulting - # data can be any string so we don't validate it. - vol.Optional("type"): vol.In(STRING_TYPES), - } - ) - - def __call__(self, data: Any) -> str: - """Validate the passed selection.""" - text: str = vol.Schema(str)(data) - return text + entity: SingleEntitySelectorConfig + device: SingleDeviceSelectorConfig @SELECTORS.register("target") @@ -559,12 +755,94 @@ class TargetSelector(Selector): TARGET_SELECTION_SCHEMA = vol.Schema(cv.TARGET_SERVICE_FIELDS) + def __init__(self, config: TargetSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> dict[str, list[str]]: """Validate the passed selection.""" target: dict[str, list[str]] = self.TARGET_SELECTION_SCHEMA(data) return target +class TemplateSelectorConfig(TypedDict): + """Class to represent an template selector config.""" + + +@SELECTORS.register("template") +class TemplateSelector(Selector): + """Selector for an template.""" + + selector_type = "template" + + CONFIG_SCHEMA = vol.Schema({}) + + def __init__(self, config: TemplateSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + template = cv.template(data) + return template.template + + +class TextSelectorConfig(TypedDict, total=False): + """Class to represent a text selector config.""" + + multiline: bool + suffix: str + type: TextSelectorType + + +class TextSelectorType(StrEnum): + """Enum for text selector types.""" + + COLOR = "color" + DATE = "date" + DATETIME_LOCAL = "datetime-local" + EMAIL = "email" + MONTH = "month" + NUMBER = "number" + PASSWORD = "password" + SEARCH = "search" + TEL = "tel" + TEXT = "text" + TIME = "time" + URL = "url" + WEEK = "week" + + +@SELECTORS.register("text") +class TextSelector(Selector): + """Selector for a multi-line text string.""" + + selector_type = "text" + + CONFIG_SCHEMA = vol.Schema( + { + vol.Optional("multiline", default=False): bool, + vol.Optional("suffix"): str, + # The "type" controls the input field in the browser, the resulting + # data can be any string so we don't validate it. + vol.Optional("type"): vol.Coerce(TextSelectorType), + } + ) + + def __init__(self, config: TextSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + + def __call__(self, data: Any) -> str: + """Validate the passed selection.""" + text: str = vol.Schema(str)(data) + return text + + +class ThemeSelectorConfig(TypedDict): + """Class to represent a theme selector config.""" + + @SELECTORS.register("theme") class ThemeSelector(Selector): """Selector for an theme.""" @@ -573,12 +851,20 @@ class ThemeSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: ThemeSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str: """Validate the passed selection.""" theme: str = vol.Schema(str)(data) return theme +class TimeSelectorConfig(TypedDict): + """Class to represent a time selector config.""" + + @SELECTORS.register("time") class TimeSelector(Selector): """Selector of a time value.""" @@ -587,6 +873,10 @@ class TimeSelector(Selector): CONFIG_SCHEMA = vol.Schema({}) + def __init__(self, config: TimeSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + def __call__(self, data: Any) -> str: """Validate the passed selection.""" cv.time(data) diff --git a/homeassistant/helpers/start.py b/homeassistant/helpers/start.py index 7f919f5351d..6c17ae5be3a 100644 --- a/homeassistant/helpers/start.py +++ b/homeassistant/helpers/start.py @@ -20,8 +20,19 @@ def async_at_start( hass.async_run_hass_job(at_start_job, hass) return lambda: None - async def _matched_event(event: Event) -> None: + unsub: None | CALLBACK_TYPE = None + + @callback + def _matched_event(event: Event) -> None: """Call the callback when Home Assistant started.""" hass.async_run_hass_job(at_start_job, hass) + nonlocal unsub + unsub = None - return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _matched_event) + @callback + def cancel() -> None: + if unsub: + unsub() + + unsub = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _matched_event) + return cancel diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 0d849f67d9a..3f084674a1b 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -33,6 +33,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_LATITUDE, ATTR_LONGITUDE, + ATTR_PERSONS, ATTR_UNIT_OF_MEASUREMENT, LENGTH_METERS, STATE_UNKNOWN, @@ -76,6 +77,7 @@ _IS_NUMERIC = re.compile(r"^[+-]?(?!0\d)\d*(?:\.\d*)?$") _RESERVED_NAMES = {"contextfunction", "evalcontextfunction", "environmentfunction"} _GROUP_DOMAIN_PREFIX = "group." +_ZONE_DOMAIN_PREFIX = "zone." _COLLECTABLE_STATE_ATTRIBUTES = { "state", @@ -415,7 +417,7 @@ class Template: return self._parse_result(render_result) - def _parse_result(self, render_result: str) -> Any: # pylint: disable=no-self-use + def _parse_result(self, render_result: str) -> Any: """Parse the result.""" try: result = literal_eval(render_result) @@ -717,22 +719,24 @@ class DomainStates: return f"